// Copyright (c) 2014, 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/object_graph.h" #include "vm/dart.h" #include "vm/dart_api_state.h" #include "vm/growable_array.h" #include "vm/isolate.h" #include "vm/native_symbol.h" #include "vm/object.h" #include "vm/object_store.h" #include "vm/profiler.h" #include "vm/raw_object.h" #include "vm/raw_object_fields.h" #include "vm/reusable_handles.h" #include "vm/visitor.h" namespace dart { #if defined(DART_ENABLE_HEAP_SNAPSHOT_WRITER) static bool IsUserClass(intptr_t cid) { if (cid == kContextCid) return true; if (cid == kTypeArgumentsCid) return false; return !IsInternalOnlyClassId(cid); } // A slot in the fixed-size portion of a heap object. // // This may be a regulard dart field, a unboxed dart field or // a slot of any type in a predefined layout. struct ObjectSlot { uint32_t offset; bool is_compressed_pointer; const char* name; ObjectSlot(uint32_t offset, bool is_compressed_pointer, const char* name) : offset(offset), is_compressed_pointer(is_compressed_pointer), name(name) {} }; class ObjectSlots { public: using ObjectSlotsType = ZoneGrowableArray; explicit ObjectSlots(Thread* thread) { auto class_table = thread->isolate_group()->class_table(); const intptr_t class_count = class_table->NumCids(); HANDLESCOPE(thread); auto& cls = Class::Handle(thread->zone()); auto& fields = Array::Handle(thread->zone()); auto& field = Field::Handle(thread->zone()); auto& name = String::Handle(thread->zone()); cid2object_slots_.FillWith(nullptr, 0, class_count); contains_only_tagged_words_.FillWith(false, 0, class_count); for (intptr_t cid = 1; cid < class_count; cid++) { if (!class_table->HasValidClassAt(cid)) continue; // Non-finalized classes are abstract, so we will not collect any field // information for them. cls = class_table->At(cid); if (!cls.is_finalized()) continue; auto slots = cid2object_slots_[cid] = new ObjectSlotsType(); // If the class has native fields, the native fields array is the first // field and therefore starts after the `kWordSize` tagging word. if (cls.num_native_fields() > 0) { slots->Add(ObjectSlot(kWordSize, true, "native_fields")); } // If the class or any super class is generic, it will have a type // arguments vector. const auto tav_offset = cls.host_type_arguments_field_offset(); if (tav_offset != Class::kNoTypeArguments) { slots->Add(ObjectSlot(tav_offset, true, "type_arguments")); } // Add slots for all user-defined instance fields in the hierarchy. while (!cls.IsNull()) { const intptr_t current_cid = cls.id(); if (current_cid < kNumPredefinedCids) { bool slots_added = false; for (const auto& entry : OffsetsTable::offsets_table()) { if (entry.class_id == current_cid) { slots->Add(ObjectSlot(entry.offset, entry.is_compressed_pointer, entry.field_name)); slots_added = true; } } if (slots_added) { break; } } fields = cls.fields(); if (!fields.IsNull()) { for (intptr_t i = 0; i < fields.Length(); ++i) { field ^= fields.At(i); if (!field.is_instance()) continue; name = field.name(); // If the field is unboxed, we don't know the size of it (may be // multiple words) - but that doesn't matter because // a) we will process instances using the slots we collect // (instead of regular GC visitor); // b) we will not write the value of the field and instead treat // it like a dummy reference to 0 (like we do with Smis). slots->Add(ObjectSlot(field.HostOffset(), !field.is_unboxed(), name.ToCString())); } } cls = cls.SuperClass(); } // We sort the slots, so we'll visit the slots in memory order. slots->Sort([](const ObjectSlot* a, const ObjectSlot* b) { if (a->offset < b->offset) return -1; if (a->offset > b->offset) return 1; return 0; }); // As optimization as well as to support variable-length data, we remember // whether this class has only pure tagged pointers in it, then we can // safely use regular GC visitors. bool contains_only_tagged_words = true; for (auto& slot : *slots) { if (!slot.is_compressed_pointer) { contains_only_tagged_words = false; break; } } #if defined(DEBUG) // For pure pointer objects, the slots have to start after tagging word // and be without holes (otherwise, e.g. if a slot was not declared, // the visitors will visit them but we won't emit the field description in // the heap snapshot). if (contains_only_tagged_words && (slots->length() > 0)) { auto expected_offset = (*slots)[0].offset; for (auto& slot : *slots) { if (slot.offset != expected_offset) { FATAL("Slot %s has offset 0x%" Px32 ", expected offset 0x%" Px32, slot.name, slot.offset, expected_offset); } expected_offset += kCompressedWordSize; } } ASSERT(contains_only_tagged_words || (cid != kArrayCid && cid != kImmutableArrayCid)); #endif // defined(DEBUG) contains_only_tagged_words_[cid] = contains_only_tagged_words; } } const ObjectSlotsType* ObjectSlotsFor(intptr_t cid) const { return cid2object_slots_[cid]; } // Returns `true` if all fields are tagged (i.e. no unboxed fields). bool ContainsOnlyTaggedPointers(intptr_t cid) { return contains_only_tagged_words_[cid]; } private: GrowableArray cid2object_slots_; GrowableArray contains_only_tagged_words_; }; // The state of a pre-order, depth-first traversal of an object graph. // When a node is visited, *all* its children are pushed to the stack at once. // We insert a sentinel between the node and its children on the stack, to // remember that the node has been visited. The node is kept on the stack while // its children are processed, to give the visitor a complete chain of parents. // // TODO(koda): Potential optimizations: // - Use tag bits for compact Node and sentinel representations. class ObjectGraph::Stack : public ObjectPointerVisitor { public: explicit Stack(IsolateGroup* isolate_group) : ObjectPointerVisitor(isolate_group), include_vm_objects_(true), data_(kInitialCapacity) { object_ids_ = new WeakTable(); } ~Stack() { delete object_ids_; object_ids_ = nullptr; } bool trace_values_through_fields() const override { return true; } bool trace_object_id_rings() const override { return false; } // Marks and pushes. Used to initialize this stack with roots. // We can use ObjectIdTable normally used by serializers because it // won't be in use while handling a service request (ObjectGraph's only use). void VisitPointers(ObjectPtr* first, ObjectPtr* last) override { for (ObjectPtr* current = first; current <= last; ++current) { Visit(current, *current); } } #if defined(DART_COMPRESSED_POINTERS) void VisitCompressedPointers(uword heap_base, CompressedObjectPtr* first, CompressedObjectPtr* last) override { for (CompressedObjectPtr* current = first; current <= last; ++current) { Visit(current, current->Decompress(heap_base)); } } #endif void Visit(void* ptr, ObjectPtr obj) { if (obj->IsHeapObject() && object_ids_->GetValueExclusive(obj) == 0) { // not visited yet if (!include_vm_objects_ && !IsUserClass(obj->GetClassIdOfHeapObject())) { return; } object_ids_->SetValueExclusive(obj, 1); Node node; node.ptr = ptr; node.obj = obj; node.gc_root_type = gc_root_type(); data_.Add(node); } } // Traverses the object graph from the current state. void TraverseGraph(ObjectGraph::Visitor* visitor) { while (!data_.is_empty()) { Node node = data_.Last(); if (node.ptr == kSentinel) { data_.RemoveLast(); // The node below the sentinel has already been visited. data_.RemoveLast(); continue; } ObjectPtr obj = node.obj; ASSERT(obj->IsHeapObject()); Node sentinel; sentinel.ptr = kSentinel; data_.Add(sentinel); StackIterator it(this, data_.length() - 2); visitor->gc_root_type = node.gc_root_type; Visitor::Direction direction = visitor->VisitObject(&it); if (direction == ObjectGraph::Visitor::kAbort) { break; } if (direction == ObjectGraph::Visitor::kProceed) { set_gc_root_type(node.gc_root_type); ASSERT(obj->IsHeapObject()); switch (obj->GetClassIdOfHeapObject()) { case kWeakArrayCid: VisitWeakArray(static_cast(obj)); break; case kWeakReferenceCid: VisitWeakReference(static_cast(obj)); break; case kFinalizerEntryCid: VisitFinalizerEntry(static_cast(obj)); break; default: obj->untag()->VisitPointers(this); break; } clear_gc_root_type(); } } } void VisitWeakArray(WeakArrayPtr array) {} void VisitWeakReference(WeakReferencePtr ref) { #if !defined(DART_COMPRESSED_POINTERS) VisitPointers(&ref->untag()->type_arguments_, &ref->untag()->type_arguments_); #else VisitCompressedPointers(ref->heap_base(), &ref->untag()->type_arguments_, &ref->untag()->type_arguments_); #endif } void VisitFinalizerEntry(FinalizerEntryPtr entry) { #if !defined(DART_COMPRESSED_POINTERS) VisitPointers(&entry->untag()->token_, &entry->untag()->token_); VisitPointers(&entry->untag()->next_, &entry->untag()->next_); #else VisitCompressedPointers(entry->heap_base(), &entry->untag()->token_, &entry->untag()->token_); VisitCompressedPointers(entry->heap_base(), &entry->untag()->next_, &entry->untag()->next_); #endif } bool visit_weak_persistent_handles() const override { return visit_weak_persistent_handles_; } void set_visit_weak_persistent_handles(bool value) { visit_weak_persistent_handles_ = value; } bool include_vm_objects_; private: struct Node { void* ptr; // kSentinel for the sentinel node. ObjectPtr obj; const char* gc_root_type; }; bool visit_weak_persistent_handles_ = false; static ObjectPtr* const kSentinel; static constexpr intptr_t kInitialCapacity = 1024; static constexpr intptr_t kNoParent = -1; intptr_t Parent(intptr_t index) const { // The parent is just below the next sentinel. for (intptr_t i = index; i >= 1; --i) { if (data_[i].ptr == kSentinel) { return i - 1; } } return kNoParent; } // During the iteration of the heap we are already at a safepoint, so there is // no need to let the GC know about [object_ids_] (i.e. GC cannot run while we // use [object_ids]). WeakTable* object_ids_ = nullptr; GrowableArray data_; friend class StackIterator; DISALLOW_COPY_AND_ASSIGN(Stack); }; ObjectPtr* const ObjectGraph::Stack::kSentinel = nullptr; ObjectPtr ObjectGraph::StackIterator::Get() const { return stack_->data_[index_].obj; } bool ObjectGraph::StackIterator::MoveToParent() { intptr_t parent = stack_->Parent(index_); if (parent == Stack::kNoParent) { return false; } else { index_ = parent; return true; } } intptr_t ObjectGraph::StackIterator::OffsetFromParent() const { intptr_t parent_index = stack_->Parent(index_); if (parent_index == Stack::kNoParent) { return -1; } Stack::Node parent = stack_->data_[parent_index]; uword parent_start = UntaggedObject::ToAddr(parent.obj); Stack::Node child = stack_->data_[index_]; uword child_ptr_addr = reinterpret_cast(child.ptr); intptr_t offset = child_ptr_addr - parent_start; if (offset > 0 && offset < parent.obj->untag()->HeapSize()) { return offset; } else { // Some internal VM objects visit pointers not contained within the parent. // For instance, UntaggedCode::VisitCodePointers visits pointers in // instructions. ASSERT(!parent.obj->IsDartInstance()); return -1; } } static void IterateUserFields(ObjectPointerVisitor* visitor) { visitor->set_gc_root_type("user global"); Thread* thread = Thread::Current(); // Scope to prevent handles create here from appearing as stack references. HANDLESCOPE(thread); Zone* zone = thread->zone(); const GrowableObjectArray& libraries = GrowableObjectArray::Handle( zone, thread->isolate_group()->object_store()->libraries()); Library& library = Library::Handle(zone); Object& entry = Object::Handle(zone); Class& cls = Class::Handle(zone); Array& fields = Array::Handle(zone); Field& field = Field::Handle(zone); for (intptr_t i = 0; i < libraries.Length(); i++) { library ^= libraries.At(i); DictionaryIterator entries(library); while (entries.HasNext()) { entry = entries.GetNext(); if (entry.IsClass()) { cls ^= entry.ptr(); fields = cls.fields(); for (intptr_t j = 0; j < fields.Length(); j++) { field ^= fields.At(j); ObjectPtr ptr = field.ptr(); visitor->VisitPointer(&ptr); } } else if (entry.IsField()) { field ^= entry.ptr(); ObjectPtr ptr = field.ptr(); visitor->VisitPointer(&ptr); } } } visitor->clear_gc_root_type(); } ObjectGraph::ObjectGraph(Thread* thread) : ThreadStackResource(thread) {} ObjectGraph::~ObjectGraph() {} void ObjectGraph::IterateObjects(ObjectGraph::Visitor* visitor) { Stack stack(isolate_group()); stack.set_visit_weak_persistent_handles( visitor->visit_weak_persistent_handles()); isolate_group()->VisitObjectPointers(&stack, ValidationPolicy::kDontValidateFrames); stack.TraverseGraph(visitor); } void ObjectGraph::IterateUserObjects(ObjectGraph::Visitor* visitor) { Stack stack(isolate_group()); stack.set_visit_weak_persistent_handles( visitor->visit_weak_persistent_handles()); IterateUserFields(&stack); stack.include_vm_objects_ = false; stack.TraverseGraph(visitor); } void ObjectGraph::IterateObjectsFrom(const Object& root, ObjectGraph::Visitor* visitor) { Stack stack(isolate_group()); stack.set_visit_weak_persistent_handles( visitor->visit_weak_persistent_handles()); ObjectPtr root_raw = root.ptr(); stack.VisitPointer(&root_raw); stack.TraverseGraph(visitor); } class InstanceAccumulator : public ObjectVisitor { public: InstanceAccumulator(ObjectGraph::Stack* stack, intptr_t class_id) : stack_(stack), class_id_(class_id) {} void VisitObject(ObjectPtr obj) override { if (obj->GetClassIdOfHeapObject() == class_id_) { ObjectPtr rawobj = obj; stack_->VisitPointer(&rawobj); } } private: ObjectGraph::Stack* stack_; const intptr_t class_id_; DISALLOW_COPY_AND_ASSIGN(InstanceAccumulator); }; void ObjectGraph::IterateObjectsFrom(intptr_t class_id, HeapIterationScope* iteration, ObjectGraph::Visitor* visitor) { Stack stack(isolate_group()); InstanceAccumulator accumulator(&stack, class_id); iteration->IterateObjectsNoImagePages(&accumulator); stack.TraverseGraph(visitor); } class SizeVisitor : public ObjectGraph::Visitor { public: SizeVisitor() : size_(0) {} intptr_t size() const { return size_; } virtual bool ShouldSkip(ObjectPtr obj) const { return false; } virtual Direction VisitObject(ObjectGraph::StackIterator* it) { ObjectPtr obj = it->Get(); if (ShouldSkip(obj)) { return kBacktrack; } size_ += obj->untag()->HeapSize(); return kProceed; } private: intptr_t size_; }; class SizeExcludingObjectVisitor : public SizeVisitor { public: explicit SizeExcludingObjectVisitor(const Object& skip) : skip_(skip) {} virtual bool ShouldSkip(ObjectPtr obj) const { return obj == skip_.ptr(); } private: const Object& skip_; }; class SizeExcludingClassVisitor : public SizeVisitor { public: explicit SizeExcludingClassVisitor(intptr_t skip) : skip_(skip) {} virtual bool ShouldSkip(ObjectPtr obj) const { return obj->GetClassIdOfHeapObject() == skip_; } private: const intptr_t skip_; }; intptr_t ObjectGraph::SizeRetainedByInstance(const Object& obj) { HeapIterationScope iteration_scope(Thread::Current(), true); SizeVisitor total; IterateObjects(&total); intptr_t size_total = total.size(); SizeExcludingObjectVisitor excluding_obj(obj); IterateObjects(&excluding_obj); intptr_t size_excluding_obj = excluding_obj.size(); return size_total - size_excluding_obj; } intptr_t ObjectGraph::SizeReachableByInstance(const Object& obj) { HeapIterationScope iteration_scope(Thread::Current(), true); SizeVisitor total; IterateObjectsFrom(obj, &total); return total.size(); } intptr_t ObjectGraph::SizeRetainedByClass(intptr_t class_id) { HeapIterationScope iteration_scope(Thread::Current(), true); SizeVisitor total; IterateObjects(&total); intptr_t size_total = total.size(); SizeExcludingClassVisitor excluding_class(class_id); IterateObjects(&excluding_class); intptr_t size_excluding_class = excluding_class.size(); return size_total - size_excluding_class; } intptr_t ObjectGraph::SizeReachableByClass(intptr_t class_id) { HeapIterationScope iteration_scope(Thread::Current(), true); SizeVisitor total; IterateObjectsFrom(class_id, &iteration_scope, &total); return total.size(); } class RetainingPathVisitor : public ObjectGraph::Visitor { public: // We cannot use a GrowableObjectArray, since we must not trigger GC. RetainingPathVisitor(ObjectPtr obj, const Array& path) : thread_(Thread::Current()), obj_(obj), path_(path), length_(0) {} intptr_t length() const { return length_; } virtual bool visit_weak_persistent_handles() const { return true; } bool ShouldSkip(ObjectPtr obj) { // A retaining path through ICData is never the only retaining path, // and it is less informative than its alternatives. intptr_t cid = obj->GetClassIdOfHeapObject(); switch (cid) { case kICDataCid: return true; default: return false; } } bool ShouldStop(ObjectPtr obj) { // A static field is considered a root from a language point of view. if (obj->IsField()) { const Field& field = Field::Handle(static_cast(obj)); return field.is_static(); } return false; } void StartList() { was_last_array_ = false; } intptr_t HideNDescendant(ObjectPtr obj) { // A GrowableObjectArray overwrites its internal storage. // Keeping both of them in the list is redundant. if (was_last_array_ && obj->IsGrowableObjectArray()) { was_last_array_ = false; return 1; } // A LinkedHasMap overwrites its internal storage. // Keeping both of them in the list is redundant. if (was_last_array_ && obj->IsMap()) { was_last_array_ = false; return 1; } was_last_array_ = obj->IsArray(); return 0; } virtual Direction VisitObject(ObjectGraph::StackIterator* it) { if (it->Get() != obj_) { if (ShouldSkip(it->Get())) { return kBacktrack; } else { return kProceed; } } else { HANDLESCOPE(thread_); Object& current = Object::Handle(); Smi& offset_from_parent = Smi::Handle(); StartList(); do { // We collapse the backingstore of some internal objects. length_ -= HideNDescendant(it->Get()); intptr_t obj_index = length_ * 2; intptr_t offset_index = obj_index + 1; if (!path_.IsNull() && offset_index < path_.Length()) { current = it->Get(); path_.SetAt(obj_index, current); offset_from_parent = Smi::New(it->OffsetFromParent()); path_.SetAt(offset_index, offset_from_parent); } ++length_; } while (!ShouldStop(it->Get()) && it->MoveToParent()); return kAbort; } } private: Thread* thread_; ObjectPtr obj_; const Array& path_; intptr_t length_; bool was_last_array_; }; ObjectGraph::RetainingPathResult ObjectGraph::RetainingPath(Object* obj, const Array& path) { HeapIterationScope iteration_scope(Thread::Current(), true); // To break the trivial path, the handle 'obj' is temporarily cleared during // the search, but restored before returning. ObjectPtr raw = obj->ptr(); *obj = Object::null(); RetainingPathVisitor visitor(raw, path); IterateUserObjects(&visitor); if (visitor.length() == 0) { IterateObjects(&visitor); } *obj = raw; return {visitor.length(), visitor.gc_root_type}; } class InboundReferencesVisitor : public ObjectVisitor, public ObjectPointerVisitor { public: // We cannot use a GrowableObjectArray, since we must not trigger GC. InboundReferencesVisitor(Isolate* isolate, ObjectPtr target, const Array& references, Object* scratch) : ObjectPointerVisitor(isolate->group()), source_(nullptr), target_(target), references_(references), scratch_(scratch), length_(0) { ASSERT(Thread::Current()->no_safepoint_scope_depth() != 0); } bool trace_values_through_fields() const override { return true; } bool trace_object_id_rings() const override { return false; } intptr_t length() const { return length_; } void VisitObject(ObjectPtr raw_obj) override { source_ = raw_obj; raw_obj->untag()->VisitPointers(this); } void VisitPointers(ObjectPtr* first, ObjectPtr* last) override { for (ObjectPtr* current_ptr = first; current_ptr <= last; current_ptr++) { ObjectPtr current_obj = *current_ptr; if (current_obj == target_) { intptr_t obj_index = length_ * 2; intptr_t offset_index = obj_index + 1; if (!references_.IsNull() && offset_index < references_.Length()) { *scratch_ = source_; references_.SetAt(obj_index, *scratch_); *scratch_ = Smi::New(0); uword source_start = UntaggedObject::ToAddr(source_); uword current_ptr_addr = reinterpret_cast(current_ptr); intptr_t offset = current_ptr_addr - source_start; if (offset > 0 && offset < source_->untag()->HeapSize()) { *scratch_ = Smi::New(offset); } else { // Some internal VM objects visit pointers not contained within the // parent. For instance, UntaggedCode::VisitCodePointers visits // pointers in instructions. ASSERT(!source_->IsDartInstance()); *scratch_ = Smi::New(-1); } references_.SetAt(offset_index, *scratch_); } ++length_; } } } #if defined(DART_COMPRESSED_POINTERS) void VisitCompressedPointers(uword heap_base, CompressedObjectPtr* first, CompressedObjectPtr* last) override { for (CompressedObjectPtr* current_ptr = first; current_ptr <= last; current_ptr++) { ObjectPtr current_obj = current_ptr->Decompress(heap_base); if (current_obj == target_) { intptr_t obj_index = length_ * 2; intptr_t offset_index = obj_index + 1; if (!references_.IsNull() && offset_index < references_.Length()) { *scratch_ = source_; references_.SetAt(obj_index, *scratch_); *scratch_ = Smi::New(0); uword source_start = UntaggedObject::ToAddr(source_); uword current_ptr_addr = reinterpret_cast(current_ptr); intptr_t offset = current_ptr_addr - source_start; if (offset > 0 && offset < source_->untag()->HeapSize()) { *scratch_ = Smi::New(offset); } else { // Some internal VM objects visit pointers not contained within the // parent. For instance, UntaggedCode::VisitCodePointers visits // pointers in instructions. ASSERT(!source_->IsDartInstance()); *scratch_ = Smi::New(-1); } references_.SetAt(offset_index, *scratch_); } ++length_; } } } #endif private: ObjectPtr source_; ObjectPtr target_; const Array& references_; Object* scratch_; intptr_t length_; }; intptr_t ObjectGraph::InboundReferences(Object* obj, const Array& references) { Object& scratch = Object::Handle(); HeapIterationScope iteration(Thread::Current()); NoSafepointScope no_safepoint; InboundReferencesVisitor visitor(isolate(), obj->ptr(), references, &scratch); iteration.IterateObjects(&visitor); return visitor.length(); } // Each Page is divided into blocks of size kBlockSize. Each object belongs // to the block containing its header word. // When generating a heap snapshot, we assign objects sequential ids in heap // iteration order. A bitvector is computed that indicates the number of objects // in each block, so the id of any object in the block can be found be adding // the number of bits set before the object to the block's first id. // Compare ForwardingBlock used for heap compaction. class CountingBlock { public: void Clear() { base_count_ = 0; count_bitvector_ = 0; } intptr_t Lookup(uword addr) const { uword block_offset = addr & ~Page::kBlockMask; intptr_t bitvector_shift = block_offset >> kObjectAlignmentLog2; ASSERT(bitvector_shift < kBitsPerWord); uword preceding_bitmask = (static_cast(1) << bitvector_shift) - 1; return base_count_ + Utils::CountOneBitsWord(count_bitvector_ & preceding_bitmask); } void Record(uword old_addr, intptr_t id) { if (base_count_ == 0) { ASSERT(count_bitvector_ == 0); base_count_ = id; // First object in the block. } uword block_offset = old_addr & ~Page::kBlockMask; intptr_t bitvector_shift = block_offset >> kObjectAlignmentLog2; ASSERT(bitvector_shift < kBitsPerWord); count_bitvector_ |= static_cast(1) << bitvector_shift; } private: intptr_t base_count_; uword count_bitvector_; COMPILE_ASSERT(Page::kBitVectorWordsPerBlock == 1); DISALLOW_COPY_AND_ASSIGN(CountingBlock); }; class CountingPage { public: void Clear() { for (intptr_t i = 0; i < Page::kBlocksPerPage; i++) { blocks_[i].Clear(); } } intptr_t Lookup(uword addr) { return BlockFor(addr)->Lookup(addr); } void Record(uword addr, intptr_t id) { return BlockFor(addr)->Record(addr, id); } CountingBlock* BlockFor(uword addr) { intptr_t page_offset = addr & ~Page::kPageMask; intptr_t block_number = page_offset / Page::kBlockSize; ASSERT(block_number >= 0); ASSERT(block_number <= Page::kBlocksPerPage); return &blocks_[block_number]; } private: CountingBlock blocks_[Page::kBlocksPerPage]; DISALLOW_ALLOCATION(); DISALLOW_IMPLICIT_CONSTRUCTORS(CountingPage); }; void HeapSnapshotWriter::EnsureAvailable(intptr_t needed) { intptr_t available = capacity_ - size_; if (available >= needed) { return; } if (buffer_ != nullptr) { Flush(); } ASSERT(buffer_ == nullptr); intptr_t chunk_size = kPreferredChunkSize; const intptr_t reserved_prefix = writer_->ReserveChunkPrefixSize(); if (chunk_size < (reserved_prefix + needed)) { chunk_size = reserved_prefix + needed; } buffer_ = reinterpret_cast(malloc(chunk_size)); size_ = reserved_prefix; capacity_ = chunk_size; } void HeapSnapshotWriter::Flush(bool last) { if (size_ == 0 && !last) { return; } writer_->WriteChunk(buffer_, size_, last); buffer_ = nullptr; size_ = 0; capacity_ = 0; } void HeapSnapshotWriter::SetupImagePageBoundaries() { MallocGrowableArray ranges(4); Page* image_page = isolate_group()->heap()->old_space()->image_pages_; while (image_page != nullptr) { ImagePageRange range = {image_page->object_start(), image_page->object_end()}; ranges.Add(range); image_page = image_page->next(); } ranges.Sort(CompareImagePageRanges); intptr_t image_page_count; ranges.StealBuffer(&image_page_ranges_, &image_page_count); image_page_hi_ = image_page_count - 1; } void HeapSnapshotWriter::SetupCountingPages() { Page* page = isolate_group()->heap()->old_space()->pages_; while (page != nullptr) { CountingPage* counting_page = reinterpret_cast(page->forwarding_page()); ASSERT(counting_page != nullptr); counting_page->Clear(); page = page->next(); } } bool HeapSnapshotWriter::OnImagePage(ObjectPtr obj) const { const uword addr = UntaggedObject::ToAddr(obj); intptr_t lo = 0; intptr_t hi = image_page_hi_; while (lo <= hi) { intptr_t mid = (hi - lo + 1) / 2 + lo; ASSERT(mid >= lo); ASSERT(mid <= hi); if (addr < image_page_ranges_[mid].start) { hi = mid - 1; } else if (addr >= image_page_ranges_[mid].end) { lo = mid + 1; } else { return true; } } return false; } CountingPage* HeapSnapshotWriter::FindCountingPage(ObjectPtr obj) const { if (obj->IsOldObject() && !OnImagePage(obj)) { // On a regular or large page. Page* page = Page::Of(obj); return reinterpret_cast(page->forwarding_page()); } // On an image page or in new space. return nullptr; } void HeapSnapshotWriter::AssignObjectId(ObjectPtr obj) { if (!obj->IsHeapObject()) { thread()->heap()->SetObjectId(obj, ++object_count_); return; } CountingPage* counting_page = FindCountingPage(obj); if (counting_page != nullptr) { // Likely: object on an ordinary page. counting_page->Record(UntaggedObject::ToAddr(obj), ++object_count_); } else { // Unlikely: new space object, or object on a large or image page. thread()->heap()->SetObjectId(obj, ++object_count_); } } intptr_t HeapSnapshotWriter::GetObjectId(ObjectPtr obj) const { if (!obj->IsHeapObject()) { intptr_t id = thread()->heap()->GetObjectId(obj); ASSERT(id != 0); return id; } CountingPage* counting_page = FindCountingPage(obj); intptr_t id; if (counting_page != nullptr) { // Likely: object on an ordinary page. id = counting_page->Lookup(UntaggedObject::ToAddr(obj)); } else { // Unlikely: new space object, or object on a large or image page. id = thread()->heap()->GetObjectId(obj); } ASSERT(id != 0); return id; } void HeapSnapshotWriter::ClearObjectIds() { thread()->heap()->ResetObjectIdTable(); } void HeapSnapshotWriter::CountReferences(intptr_t count) { reference_count_ += count; } void HeapSnapshotWriter::CountExternalProperty() { external_property_count_ += 1; } void HeapSnapshotWriter::AddSmi(SmiPtr smi) { if (thread()->heap()->GetObjectId(smi) == WeakTable::kNoValue) { thread()->heap()->SetObjectId(smi, -1); smis_.Add(smi); } } class Pass1Visitor : public ObjectVisitor, public ObjectPointerVisitor, public HandleVisitor { public: explicit Pass1Visitor(HeapSnapshotWriter* writer, ObjectSlots* object_slots) : ObjectVisitor(), ObjectPointerVisitor(IsolateGroup::Current()), HandleVisitor(), writer_(writer), object_slots_(object_slots) {} void VisitObject(ObjectPtr obj) override { if (obj->IsPseudoObject()) return; writer_->AssignObjectId(obj); const auto cid = obj->GetClassIdOfHeapObject(); if (object_slots_->ContainsOnlyTaggedPointers(cid)) { obj->untag()->VisitPointersPrecise(this); } else { for (auto& slot : *object_slots_->ObjectSlotsFor(cid)) { if (slot.is_compressed_pointer) { auto target = reinterpret_cast( UntaggedObject::ToAddr(obj->untag()) + slot.offset); VisitCompressedPointers(obj->heap_base(), target, target); } else { writer_->CountReferences(1); } } } } void VisitPointers(ObjectPtr* from, ObjectPtr* to) override { for (ObjectPtr* ptr = from; ptr <= to; ptr++) { ObjectPtr obj = *ptr; if (!obj->IsHeapObject()) { writer_->AddSmi(static_cast(obj)); } writer_->CountReferences(1); } } #if defined(DART_COMPRESSED_POINTERS) void VisitCompressedPointers(uword heap_base, CompressedObjectPtr* from, CompressedObjectPtr* to) override { for (CompressedObjectPtr* ptr = from; ptr <= to; ptr++) { ObjectPtr obj = ptr->Decompress(heap_base); if (!obj->IsHeapObject()) { writer_->AddSmi(static_cast(obj)); } writer_->CountReferences(1); } } #endif void VisitHandle(uword addr) override { FinalizablePersistentHandle* weak_persistent_handle = reinterpret_cast(addr); if (!weak_persistent_handle->ptr()->IsHeapObject()) { return; // Free handle. } writer_->CountExternalProperty(); } private: HeapSnapshotWriter* const writer_; ObjectSlots* object_slots_; DISALLOW_COPY_AND_ASSIGN(Pass1Visitor); }; class CountImagePageRefs : public ObjectVisitor { public: CountImagePageRefs() : ObjectVisitor() {} void VisitObject(ObjectPtr obj) override { if (obj->IsPseudoObject()) return; count_++; } intptr_t count() const { return count_; } private: intptr_t count_ = 0; DISALLOW_COPY_AND_ASSIGN(CountImagePageRefs); }; class WriteImagePageRefs : public ObjectVisitor { public: explicit WriteImagePageRefs(HeapSnapshotWriter* writer) : ObjectVisitor(), writer_(writer) {} void VisitObject(ObjectPtr obj) override { if (obj->IsPseudoObject()) return; #if defined(DEBUG) count_++; #endif writer_->WriteUnsigned(writer_->GetObjectId(obj)); } #if defined(DEBUG) intptr_t count() const { return count_; } #endif private: HeapSnapshotWriter* const writer_; #if defined(DEBUG) intptr_t count_ = 0; #endif DISALLOW_COPY_AND_ASSIGN(WriteImagePageRefs); }; enum NonReferenceDataTags { kNoData = 0, kNullData, kBoolData, kIntData, kDoubleData, kLatin1Data, kUTF16Data, kLengthData, kNameData, }; static constexpr intptr_t kMaxStringElements = 128; enum ExtraCids { kRootExtraCid = 1, // 1-origin kImagePageExtraCid = 2, kRootSliceExtraCid = 3, kIsolateExtraCid = 4, kObjectStoreExtraCid = 5, kNumExtraCids = 5, }; class Pass2Visitor : public ObjectVisitor, public ObjectPointerVisitor, public HandleVisitor { public: explicit Pass2Visitor(HeapSnapshotWriter* writer, ObjectSlots* object_slots) : ObjectVisitor(), ObjectPointerVisitor(IsolateGroup::Current()), HandleVisitor(), writer_(writer), object_slots_(object_slots) {} // A safepoint might occur between the allocation stub creating an object and // filling it with nulls and the instance initializer running and populating a // length field. static intptr_t SmiValueOrZero(SmiPtr smi) { return smi->IsSmi() ? Smi::Value(smi) : 0; } void VisitObject(ObjectPtr obj) override { if (obj->IsPseudoObject()) return; intptr_t cid = obj->GetClassIdOfHeapObject(); writer_->WriteUnsigned(cid + kNumExtraCids); writer_->WriteUnsigned(obj->untag()->HeapSize()); if (cid == kNullCid) { writer_->WriteUnsigned(kNullData); } else if (cid == kBoolCid) { writer_->WriteUnsigned(kBoolData); writer_->WriteUnsigned( static_cast(static_cast(obj)->untag()->value_)); } else if (cid == kSentinelCid) { if (obj == Object::sentinel().ptr()) { writer_->WriteUnsigned(kNameData); writer_->WriteUtf8("uninitialized"); } else { writer_->WriteUnsigned(kNoData); } } else if (cid == kSmiCid) { UNREACHABLE(); } else if (cid == kMintCid) { writer_->WriteUnsigned(kIntData); writer_->WriteSigned(static_cast(obj)->untag()->value_); } else if (cid == kDoubleCid) { writer_->WriteUnsigned(kDoubleData); writer_->WriteBytes(&(static_cast(obj)->untag()->value_), sizeof(double)); } else if (cid == kOneByteStringCid) { OneByteStringPtr str = static_cast(obj); intptr_t len = Smi::Value(str->untag()->length()); intptr_t trunc_len = Utils::Minimum(len, kMaxStringElements); writer_->WriteUnsigned(kLatin1Data); writer_->WriteUnsigned(len); writer_->WriteUnsigned(trunc_len); writer_->WriteBytes(&str->untag()->data()[0], trunc_len); } else if (cid == kTwoByteStringCid) { TwoByteStringPtr str = static_cast(obj); intptr_t len = Smi::Value(str->untag()->length()); intptr_t trunc_len = Utils::Minimum(len, kMaxStringElements); writer_->WriteUnsigned(kUTF16Data); writer_->WriteUnsigned(len); writer_->WriteUnsigned(trunc_len); writer_->WriteBytes(&str->untag()->data()[0], trunc_len * 2); } else if (cid == kArrayCid || cid == kImmutableArrayCid) { writer_->WriteUnsigned(kLengthData); writer_->WriteUnsigned( Smi::Value(static_cast(obj)->untag()->length())); } else if (cid == kGrowableObjectArrayCid) { writer_->WriteUnsigned(kLengthData); writer_->WriteUnsigned(SmiValueOrZero( static_cast(obj)->untag()->length())); } else if (cid == kMapCid || cid == kConstMapCid) { writer_->WriteUnsigned(kLengthData); writer_->WriteUnsigned( SmiValueOrZero(static_cast(obj)->untag()->used_data())); } else if (cid == kSetCid || cid == kConstSetCid) { writer_->WriteUnsigned(kLengthData); writer_->WriteUnsigned( SmiValueOrZero(static_cast(obj)->untag()->used_data())); } else if (cid == kObjectPoolCid) { writer_->WriteUnsigned(kLengthData); writer_->WriteUnsigned(static_cast(obj)->untag()->length_); } else if (IsTypedDataClassId(cid)) { writer_->WriteUnsigned(kLengthData); writer_->WriteUnsigned( Smi::Value(static_cast(obj)->untag()->length())); } else if (IsExternalTypedDataClassId(cid)) { writer_->WriteUnsigned(kLengthData); writer_->WriteUnsigned(Smi::Value( static_cast(obj)->untag()->length())); } else if (cid == kFunctionCid) { writer_->WriteUnsigned(kNameData); ScrubAndWriteUtf8(static_cast(obj)->untag()->name()); } else if (cid == kCodeCid) { ObjectPtr owner = static_cast(obj)->untag()->owner_; if (!owner->IsHeapObject()) { // Precompiler removed owner object from the snapshot, // only leaving Smi classId. writer_->WriteUnsigned(kNoData); } else if (owner->IsFunction()) { writer_->WriteUnsigned(kNameData); ScrubAndWriteUtf8(static_cast(owner)->untag()->name()); } else if (owner->IsClass()) { writer_->WriteUnsigned(kNameData); ScrubAndWriteUtf8(static_cast(owner)->untag()->name()); } else { writer_->WriteUnsigned(kNoData); } } else if (cid == kFieldCid) { writer_->WriteUnsigned(kNameData); ScrubAndWriteUtf8(static_cast(obj)->untag()->name()); } else if (cid == kClassCid) { writer_->WriteUnsigned(kNameData); ScrubAndWriteUtf8(static_cast(obj)->untag()->name()); } else if (cid == kLibraryCid) { writer_->WriteUnsigned(kNameData); ScrubAndWriteUtf8(static_cast(obj)->untag()->url()); } else if (cid == kScriptCid) { writer_->WriteUnsigned(kNameData); ScrubAndWriteUtf8(static_cast(obj)->untag()->url()); } else if (cid == kTypeArgumentsCid) { // Handle scope so we do not change the root set. // We are assuming that TypeArguments::PrintSubvectorName never allocates // objects or zone handles. HANDLESCOPE(Thread::Current()); const TypeArguments& args = TypeArguments::Handle(static_cast(obj)); TextBuffer buffer(128); args.PrintSubvectorName(0, args.Length(), TypeArguments::kScrubbedName, &buffer); writer_->WriteUnsigned(kNameData); writer_->WriteUtf8(buffer.buffer()); } else { writer_->WriteUnsigned(kNoData); } if (object_slots_->ContainsOnlyTaggedPointers(cid)) { DoCount(); obj->untag()->VisitPointersPrecise(this); DoWrite(); obj->untag()->VisitPointersPrecise(this); } else { auto slots = object_slots_->ObjectSlotsFor(cid); DoCount(); counted_ += slots->length(); DoWrite(); for (auto& slot : *slots) { if (slot.is_compressed_pointer) { auto target = reinterpret_cast( UntaggedObject::ToAddr(obj->untag()) + slot.offset); VisitCompressedPointers(obj->heap_base(), target, target); } else { writer_->WriteUnsigned(0); } written_++; total_++; } } } void ScrubAndWriteUtf8(StringPtr str) { if (str == String::null()) { writer_->WriteUtf8("null"); } else { String handle; handle = str; char* value = handle.ToMallocCString(); writer_->ScrubAndWriteUtf8(value); free(value); } } void DoCount() { writing_ = false; counted_ = 0; written_ = 0; } void DoWrite() { writing_ = true; writer_->WriteUnsigned(counted_); } void VisitPointers(ObjectPtr* from, ObjectPtr* to) override { if (writing_) { for (ObjectPtr* ptr = from; ptr <= to; ptr++) { ObjectPtr target = *ptr; written_++; total_++; writer_->WriteUnsigned(writer_->GetObjectId(target)); } } else { intptr_t count = to - from + 1; ASSERT(count >= 0); counted_ += count; } } #if defined(DART_COMPRESSED_POINTERS) void VisitCompressedPointers(uword heap_base, CompressedObjectPtr* from, CompressedObjectPtr* to) override { if (writing_) { for (CompressedObjectPtr* ptr = from; ptr <= to; ptr++) { ObjectPtr target = ptr->Decompress(heap_base); written_++; total_++; writer_->WriteUnsigned(writer_->GetObjectId(target)); } } else { intptr_t count = to - from + 1; ASSERT(count >= 0); counted_ += count; } } #endif void VisitHandle(uword addr) override { FinalizablePersistentHandle* weak_persistent_handle = reinterpret_cast(addr); if (!weak_persistent_handle->ptr()->IsHeapObject()) { return; // Free handle. } writer_->WriteUnsigned(writer_->GetObjectId(weak_persistent_handle->ptr())); writer_->WriteUnsigned(weak_persistent_handle->external_size()); // Attempt to include a native symbol name. auto const name = NativeSymbolResolver::LookupSymbolName( reinterpret_cast(weak_persistent_handle->callback()), nullptr); writer_->WriteUtf8((name == nullptr) ? "Unknown native function" : name); if (name != nullptr) { NativeSymbolResolver::FreeSymbolName(name); } } void CountExtraRefs(intptr_t count) { ASSERT(!writing_); counted_ += count; } void WriteExtraRef(intptr_t oid) { ASSERT(writing_); written_++; writer_->WriteUnsigned(oid); } private: IsolateGroup* isolate_group_; HeapSnapshotWriter* const writer_; ObjectSlots* object_slots_; bool writing_ = false; intptr_t counted_ = 0; intptr_t written_ = 0; intptr_t total_ = 0; DISALLOW_COPY_AND_ASSIGN(Pass2Visitor); }; class Pass3Visitor : public ObjectVisitor { public: explicit Pass3Visitor(HeapSnapshotWriter* writer) : ObjectVisitor(), thread_(Thread::Current()), writer_(writer) {} void VisitObject(ObjectPtr obj) override { if (obj->IsPseudoObject()) { return; } writer_->WriteUnsigned( HeapSnapshotWriter::GetHeapSnapshotIdentityHash(thread_, obj)); } private: Thread* thread_; HeapSnapshotWriter* const writer_; DISALLOW_COPY_AND_ASSIGN(Pass3Visitor); }; class CollectStaticFieldNames : public ObjectVisitor { public: CollectStaticFieldNames(intptr_t field_table_size, const char** field_table_names) : ObjectVisitor(), field_table_size_(field_table_size), field_table_names_(field_table_names), field_(Field::Handle()) {} void VisitObject(ObjectPtr obj) override { if (obj->IsField()) { field_ ^= obj; if (field_.is_static()) { intptr_t id = field_.field_id(); if (id > 0) { ASSERT(id < field_table_size_); field_table_names_[id] = field_.UserVisibleNameCString(); } } } } private: intptr_t field_table_size_; const char** field_table_names_; Field& field_; DISALLOW_COPY_AND_ASSIGN(CollectStaticFieldNames); }; void VmServiceHeapSnapshotChunkedWriter::WriteChunk(uint8_t* buffer, intptr_t size, bool last) { JSONStream js; { JSONObject jsobj(&js); jsobj.AddProperty("jsonrpc", "2.0"); jsobj.AddProperty("method", "streamNotify"); { JSONObject params(&jsobj, "params"); params.AddProperty("streamId", Service::heapsnapshot_stream.id()); { JSONObject event(¶ms, "event"); event.AddProperty("type", "Event"); event.AddProperty("kind", "HeapSnapshot"); event.AddProperty("isolate", thread()->isolate()); event.AddPropertyTimeMillis("timestamp", OS::GetCurrentTimeMillis()); event.AddProperty("last", last); } } } Service::SendEventWithData(Service::heapsnapshot_stream.id(), "HeapSnapshot", kMetadataReservation, js.buffer()->buffer(), js.buffer()->length(), buffer, size); } FileHeapSnapshotWriter::FileHeapSnapshotWriter(Thread* thread, const char* filename, bool* success) : ChunkedWriter(thread), success_(success) { auto open = Dart::file_open_callback(); auto write = Dart::file_write_callback(); auto close = Dart::file_close_callback(); if (open != nullptr && write != nullptr && close != nullptr) { file_ = open(filename, /*write=*/true); } // If we have open/write/close callbacks we assume it can be done // successfully. (Those embedder-provided callbacks currently don't allow // signaling of failure conditions) if (success_ != nullptr) *success_ = file_ != nullptr; } FileHeapSnapshotWriter::~FileHeapSnapshotWriter() { if (file_ != nullptr) { Dart::file_close_callback()(file_); } } void FileHeapSnapshotWriter::WriteChunk(uint8_t* buffer, intptr_t size, bool last) { if (file_ != nullptr) { Dart::file_write_callback()(buffer, size, file_); } free(buffer); } CallbackHeapSnapshotWriter::CallbackHeapSnapshotWriter( Thread* thread, Dart_HeapSnapshotWriteChunkCallback callback, void* context) : ChunkedWriter(thread), callback_(callback), context_(context) {} CallbackHeapSnapshotWriter::~CallbackHeapSnapshotWriter() {} void CallbackHeapSnapshotWriter::WriteChunk(uint8_t* buffer, intptr_t size, bool last) { callback_(context_, buffer, size, last); } void HeapSnapshotWriter::Write() { HeapIterationScope iteration(thread()); WriteBytes("dartheap", 8); // Magic value. WriteUnsigned(0); // Flags. WriteUtf8(isolate_group()->source()->name); Heap* H = thread()->heap(); { intptr_t used = H->TotalUsedInWords() << kWordSizeLog2; intptr_t capacity = H->TotalCapacityInWords() << kWordSizeLog2; intptr_t external = H->TotalExternalInWords() << kWordSizeLog2; intptr_t image = H->old_space()->ImageInWords() << kWordSizeLog2; WriteUnsigned(used + image); WriteUnsigned(capacity + image); WriteUnsigned(external); } ObjectSlots object_slots(thread()); { HANDLESCOPE(thread()); ClassTable* class_table = isolate_group()->class_table(); class_count_ = class_table->NumCids() - 1; Class& cls = Class::Handle(); Library& lib = Library::Handle(); String& str = String::Handle(); intptr_t field_table_size = isolate_group()->initial_field_table()->NumFieldIds(); const char** field_table_names = thread()->zone()->Alloc(field_table_size); for (intptr_t i = 0; i < field_table_size; i++) { field_table_names[i] = nullptr; } { CollectStaticFieldNames visitor(field_table_size, field_table_names); iteration.IterateObjects(&visitor); } WriteUnsigned(class_count_ + kNumExtraCids); { ASSERT(kRootExtraCid == 1); WriteUnsigned(0); // Flags WriteUtf8("Root"); // Name WriteUtf8(""); // Library name WriteUtf8(""); // Library uri WriteUtf8(""); // Reserved WriteUnsigned(0); // Field count } { ASSERT(kImagePageExtraCid == 2); WriteUnsigned(0); // Flags WriteUtf8("Read-Only Pages"); // Name WriteUtf8(""); // Library name WriteUtf8(""); // Library uri WriteUtf8(""); // Reserved WriteUnsigned(0); // Field count } { ASSERT(kRootSliceExtraCid == 3); WriteUnsigned(0); // Flags WriteUtf8("Root slice"); // Name WriteUtf8(""); // Library name WriteUtf8(""); // Library uri WriteUtf8(""); // Reserved WriteUnsigned(0); // Field count } { ASSERT(kIsolateExtraCid == 4); WriteUnsigned(0); // Flags WriteUtf8("Isolate"); // Name WriteUtf8(""); // Library name WriteUtf8(""); // Library uri WriteUtf8(""); // Reserved WriteUnsigned(field_table_size); // Field count for (intptr_t i = 0; i < field_table_size; i++) { intptr_t flags = 1; // Strong. WriteUnsigned(flags); WriteUnsigned(i); // Index. const char* name = field_table_names[i]; WriteUtf8(name == nullptr ? "" : name); WriteUtf8(""); // Reserved } } { ASSERT(kObjectStoreExtraCid == 5); WriteUnsigned(0); // Flags WriteUtf8("ObjectStore"); // Name WriteUtf8(""); // Library name WriteUtf8(""); // Library uri WriteUtf8(""); // Reserved enum { #define V(type, name) kObjectStore_##name, OBJECT_STORE_FIELD_LIST(V, V, V, V, V, V, V, V, V) #undef EMIT_FIELD_NAME kNumObjectStoreFields }; WriteUnsigned(kNumObjectStoreFields); // A strtab is smaller than an array of strings. static const char* const names = "" #define EMIT_FIELD_NAME(type, name) #name "_\0" OBJECT_STORE_FIELD_LIST(EMIT_FIELD_NAME, EMIT_FIELD_NAME, EMIT_FIELD_NAME, EMIT_FIELD_NAME, EMIT_FIELD_NAME, EMIT_FIELD_NAME, EMIT_FIELD_NAME, EMIT_FIELD_NAME, EMIT_FIELD_NAME) #undef EMIT_FIELD_NAME ; // NOLINT const char* name = names; for (intptr_t i = 0; i < kNumObjectStoreFields; i++) { intptr_t flags = 1; // Strong. WriteUnsigned(flags); WriteUnsigned(i); // Index. WriteUtf8(name); WriteUtf8(""); // Reserved name += strlen(name) + 1; } } for (intptr_t cid = 1; cid <= class_count_; cid++) { if (!class_table->HasValidClassAt(cid)) { WriteUnsigned(0); // Flags WriteUtf8(""); // Name WriteUtf8(""); // Library name WriteUtf8(""); // Library uri WriteUtf8(""); // Reserved WriteUnsigned(0); // Field count } else { cls = class_table->At(cid); WriteUnsigned(0); // Flags str = cls.Name(); ScrubAndWriteUtf8(const_cast(str.ToCString())); lib = cls.library(); if (lib.IsNull()) { WriteUtf8(""); WriteUtf8(""); } else { str = lib.name(); ScrubAndWriteUtf8(const_cast(str.ToCString())); str = lib.url(); ScrubAndWriteUtf8(const_cast(str.ToCString())); } WriteUtf8(""); // Reserved if (auto slots = object_slots.ObjectSlotsFor(cid)) { WriteUnsigned(slots->length()); for (intptr_t index = 0; index < slots->length(); ++index) { const auto& slot = (*slots)[index]; const intptr_t kStrongFlag = 1; WriteUnsigned(kStrongFlag); WriteUnsigned(index); ScrubAndWriteUtf8(const_cast(slot.name)); WriteUtf8(""); // Reserved } } else { // May be an abstract class. ASSERT(!cls.is_finalized()); WriteUnsigned(0); } } } } SetupImagePageBoundaries(); SetupCountingPages(); intptr_t num_isolates = 0; intptr_t num_image_objects = 0; { Pass1Visitor visitor(this, &object_slots); // Root "objects". { ++object_count_; } { ++object_count_; CountImagePageRefs visitor; H->old_space()->VisitObjectsImagePages(&visitor); num_image_objects = visitor.count(); CountReferences(num_image_objects); } for (intptr_t i = 0; i < kNumRootSlices; i++) { ++object_count_; isolate_group()->VisitSharedPointers(&visitor, static_cast(i)); } { isolate_group()->ForEachIsolate( [&](Isolate* isolate) { ++object_count_; isolate->VisitObjectPointers(&visitor, ValidationPolicy::kDontValidateFrames); isolate->VisitStackPointers(&visitor, ValidationPolicy::kDontValidateFrames); ++num_isolates; }, /*at_safepoint=*/true); } CountReferences(1); // Root -> Image Pages CountReferences(kNumRootSlices); // Root -> Root slices CountReferences(num_isolates); // Root -> Isolate // Heap objects. iteration.IterateObjects(&visitor); // External properties. isolate_group()->VisitWeakPersistentHandles(&visitor); // Smis. for (SmiPtr smi : smis_) { AssignObjectId(smi); } } { Pass2Visitor visitor(this, &object_slots); WriteUnsigned(reference_count_); WriteUnsigned(object_count_); // Root "objects". { WriteUnsigned(kRootExtraCid); WriteUnsigned(0); // shallowSize WriteUnsigned(kNoData); visitor.DoCount(); visitor.CountExtraRefs(1 + kNumRootSlices + num_isolates); visitor.DoWrite(); visitor.WriteExtraRef(2); // Root -> Image Pages for (intptr_t i = 0; i < num_isolates + kNumRootSlices; i++) { // 0 = sentinel, 1 = root, 2 = image pages, 3+ = slices/isolates visitor.WriteExtraRef(3 + i); } } { WriteUnsigned(kImagePageExtraCid); WriteUnsigned(0); // shallowSize WriteUnsigned(kNoData); WriteUnsigned(num_image_objects); WriteImagePageRefs visitor(this); H->old_space()->VisitObjectsImagePages(&visitor); DEBUG_ASSERT(visitor.count() == num_image_objects); } for (intptr_t i = 0; i < kNumRootSlices; i++) { if (i == kObjectStore) { WriteUnsigned(kObjectStoreExtraCid); } else { WriteUnsigned(kRootSliceExtraCid); } WriteUnsigned(0); // shallowSize WriteUnsigned(kNameData); WriteUtf8(RootSliceToCString(i)); visitor.DoCount(); isolate_group()->VisitSharedPointers(&visitor, i); visitor.DoWrite(); isolate_group()->VisitSharedPointers(&visitor, i); } isolate_group()->ForEachIsolate( [&](Isolate* isolate) { WriteUnsigned(kIsolateExtraCid); WriteUnsigned(0); // shallowSize WriteUnsigned(kNameData); WriteUtf8( OS::SCreate(thread()->zone(), "%" Pd64, isolate->main_port())); visitor.DoCount(); isolate->VisitObjectPointers(&visitor, ValidationPolicy::kDontValidateFrames); isolate->VisitStackPointers(&visitor, ValidationPolicy::kDontValidateFrames); visitor.DoWrite(); isolate->VisitObjectPointers(&visitor, ValidationPolicy::kDontValidateFrames); isolate->VisitStackPointers(&visitor, ValidationPolicy::kDontValidateFrames); }, /*at_safepoint=*/true); // Heap objects. iteration.IterateObjects(&visitor); // Smis. for (SmiPtr smi : smis_) { WriteUnsigned(static_cast(kSmiCid) + static_cast(kNumExtraCids)); WriteUnsigned(0); // Heap size. WriteUnsigned(kIntData); WriteUnsigned(Smi::Value(smi)); WriteUnsigned(0); // No slots. } // External properties. WriteUnsigned(external_property_count_); isolate_group()->VisitWeakPersistentHandles(&visitor); } { // Identity hash codes Pass3Visitor visitor(this); WriteUnsigned(0); // Root fake object. WriteUnsigned(0); // Image pages fake object. for (intptr_t i = 0; i < kNumRootSlices; i++) { WriteUnsigned(0); // Root slice fake object. } isolate_group()->ForEachIsolate( [&](Isolate* isolate) { WriteUnsigned(0); // Isolate fake object. }, /*at_safepoint=*/true); // Handle visit rest of the objects. iteration.IterateObjects(&visitor); for (SmiPtr smi : smis_) { USE(smi); WriteUnsigned(0); // No identity hash. } } ClearObjectIds(); Flush(true); } uint32_t HeapSnapshotWriter::GetHeapSnapshotIdentityHash(Thread* thread, ObjectPtr obj) { if (!obj->IsHeapObject()) return 0; intptr_t cid = obj->GetClassIdOfHeapObject(); uint32_t hash = 0; switch (cid) { case kForwardingCorpse: case kFreeListElement: case kSmiCid: UNREACHABLE(); case kArrayCid: case kBoolCid: case kCodeSourceMapCid: case kCompressedStackMapsCid: case kDoubleCid: case kGrowableObjectArrayCid: case kImmutableArrayCid: case kConstMapCid: case kConstSetCid: case kInstructionsCid: case kInstructionsSectionCid: case kInstructionsTableCid: case kMapCid: case kSetCid: case kMintCid: case kNeverCid: case kSentinelCid: case kNullCid: case kObjectPoolCid: case kOneByteStringCid: case kPcDescriptorsCid: case kTwoByteStringCid: case kVoidCid: // Don't provide hash codes for objects with the above CIDs in order // to try and avoid having to initialize identity hash codes for common // primitives and types that don't have hash codes. break; default: { hash = GetHashHelper(thread, obj); } } return hash; } // Generates a random value which can serve as an identity hash. // It must be a non-zero smi value (see also [Object._objectHashCode]). static uint32_t GenerateHash(Random* random) { uint32_t hash; do { hash = random->NextUInt32(); } while (hash == 0 || (kSmiBits < 32 && !Smi::IsValid(hash))); return hash; } uint32_t HeapSnapshotWriter::GetHashHelper(Thread* thread, ObjectPtr obj) { uint32_t hash; #if defined(HASH_IN_OBJECT_HEADER) hash = Object::GetCachedHash(obj); if (hash == 0) { ASSERT(!thread->heap()->old_space()->IsObjectFromImagePages(obj)); hash = GenerateHash(thread->random()); Object::SetCachedHashIfNotSet(obj, hash); } #else Heap* heap = thread->heap(); hash = heap->GetHash(obj); if (hash == 0) { ASSERT(!heap->old_space()->IsObjectFromImagePages(obj)); hash = GenerateHash(thread->random()); heap->SetHashIfNotSet(obj, hash); } #endif return hash; } CountObjectsVisitor::CountObjectsVisitor(Thread* thread, intptr_t class_count) : ObjectVisitor(), HandleVisitor(), new_count_(new intptr_t[class_count]), new_size_(new intptr_t[class_count]), new_external_size_(new intptr_t[class_count]), old_count_(new intptr_t[class_count]), old_size_(new intptr_t[class_count]), old_external_size_(new intptr_t[class_count]) { memset(new_count_.get(), 0, class_count * sizeof(intptr_t)); memset(new_size_.get(), 0, class_count * sizeof(intptr_t)); memset(new_external_size_.get(), 0, class_count * sizeof(intptr_t)); memset(old_count_.get(), 0, class_count * sizeof(intptr_t)); memset(old_size_.get(), 0, class_count * sizeof(intptr_t)); memset(old_external_size_.get(), 0, class_count * sizeof(intptr_t)); } void CountObjectsVisitor::VisitObject(ObjectPtr obj) { intptr_t cid = obj->GetClassIdOfHeapObject(); intptr_t size = obj->untag()->HeapSize(); if (obj->IsNewObject()) { new_count_[cid] += 1; new_size_[cid] += size; } else { old_count_[cid] += 1; old_size_[cid] += size; } } void CountObjectsVisitor::VisitHandle(uword addr) { FinalizablePersistentHandle* handle = reinterpret_cast(addr); ObjectPtr obj = handle->ptr(); if (!obj->IsHeapObject()) { return; } intptr_t cid = obj->GetClassIdOfHeapObject(); intptr_t size = handle->external_size(); if (obj->IsNewObject()) { new_external_size_[cid] += size; } else { old_external_size_[cid] += size; } } #endif // defined(DART_ENABLE_HEAP_SNAPSHOT_WRITER) } // namespace dart