| // 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<ObjectSlot>; | |
| 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<ObjectSlotsType*> cid2object_slots_; | |
| GrowableArray<bool> 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<WeakArrayPtr>(obj)); | |
| break; | |
| case kWeakReferenceCid: | |
| VisitWeakReference(static_cast<WeakReferencePtr>(obj)); | |
| break; | |
| case kFinalizerEntryCid: | |
| VisitFinalizerEntry(static_cast<FinalizerEntryPtr>(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<Node> 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<uword>(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<FieldPtr>(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<uword>(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<uword>(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<uword>(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<uword>(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<uint8_t*>(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<ImagePageRange> 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<CountingPage*>(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<CountingPage*>(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<CompressedObjectPtr*>( | |
| 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<SmiPtr>(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<SmiPtr>(obj)); | |
| } | |
| writer_->CountReferences(1); | |
| } | |
| } | |
| #endif | |
| void VisitHandle(uword addr) override { | |
| FinalizablePersistentHandle* weak_persistent_handle = | |
| reinterpret_cast<FinalizablePersistentHandle*>(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<uintptr_t>(static_cast<BoolPtr>(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<MintPtr>(obj)->untag()->value_); | |
| } else if (cid == kDoubleCid) { | |
| writer_->WriteUnsigned(kDoubleData); | |
| writer_->WriteBytes(&(static_cast<DoublePtr>(obj)->untag()->value_), | |
| sizeof(double)); | |
| } else if (cid == kOneByteStringCid) { | |
| OneByteStringPtr str = static_cast<OneByteStringPtr>(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<TwoByteStringPtr>(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<ArrayPtr>(obj)->untag()->length())); | |
| } else if (cid == kGrowableObjectArrayCid) { | |
| writer_->WriteUnsigned(kLengthData); | |
| writer_->WriteUnsigned(SmiValueOrZero( | |
| static_cast<GrowableObjectArrayPtr>(obj)->untag()->length())); | |
| } else if (cid == kMapCid || cid == kConstMapCid) { | |
| writer_->WriteUnsigned(kLengthData); | |
| writer_->WriteUnsigned( | |
| SmiValueOrZero(static_cast<MapPtr>(obj)->untag()->used_data())); | |
| } else if (cid == kSetCid || cid == kConstSetCid) { | |
| writer_->WriteUnsigned(kLengthData); | |
| writer_->WriteUnsigned( | |
| SmiValueOrZero(static_cast<SetPtr>(obj)->untag()->used_data())); | |
| } else if (cid == kObjectPoolCid) { | |
| writer_->WriteUnsigned(kLengthData); | |
| writer_->WriteUnsigned(static_cast<ObjectPoolPtr>(obj)->untag()->length_); | |
| } else if (IsTypedDataClassId(cid)) { | |
| writer_->WriteUnsigned(kLengthData); | |
| writer_->WriteUnsigned( | |
| Smi::Value(static_cast<TypedDataPtr>(obj)->untag()->length())); | |
| } else if (IsExternalTypedDataClassId(cid)) { | |
| writer_->WriteUnsigned(kLengthData); | |
| writer_->WriteUnsigned(Smi::Value( | |
| static_cast<ExternalTypedDataPtr>(obj)->untag()->length())); | |
| } else if (cid == kFunctionCid) { | |
| writer_->WriteUnsigned(kNameData); | |
| ScrubAndWriteUtf8(static_cast<FunctionPtr>(obj)->untag()->name()); | |
| } else if (cid == kCodeCid) { | |
| ObjectPtr owner = static_cast<CodePtr>(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<FunctionPtr>(owner)->untag()->name()); | |
| } else if (owner->IsClass()) { | |
| writer_->WriteUnsigned(kNameData); | |
| ScrubAndWriteUtf8(static_cast<ClassPtr>(owner)->untag()->name()); | |
| } else { | |
| writer_->WriteUnsigned(kNoData); | |
| } | |
| } else if (cid == kFieldCid) { | |
| writer_->WriteUnsigned(kNameData); | |
| ScrubAndWriteUtf8(static_cast<FieldPtr>(obj)->untag()->name()); | |
| } else if (cid == kClassCid) { | |
| writer_->WriteUnsigned(kNameData); | |
| ScrubAndWriteUtf8(static_cast<ClassPtr>(obj)->untag()->name()); | |
| } else if (cid == kLibraryCid) { | |
| writer_->WriteUnsigned(kNameData); | |
| ScrubAndWriteUtf8(static_cast<LibraryPtr>(obj)->untag()->url()); | |
| } else if (cid == kScriptCid) { | |
| writer_->WriteUnsigned(kNameData); | |
| ScrubAndWriteUtf8(static_cast<ScriptPtr>(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<TypeArgumentsPtr>(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<CompressedObjectPtr*>( | |
| 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<FinalizablePersistentHandle*>(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<uword>(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<const char*>(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<char*>(str.ToCString())); | |
| lib = cls.library(); | |
| if (lib.IsNull()) { | |
| WriteUtf8(""); | |
| WriteUtf8(""); | |
| } else { | |
| str = lib.name(); | |
| ScrubAndWriteUtf8(const_cast<char*>(str.ToCString())); | |
| str = lib.url(); | |
| ScrubAndWriteUtf8(const_cast<char*>(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<char*>(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<RootSlice>(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<intptr_t>(kSmiCid) + | |
| static_cast<intptr_t>(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<FinalizablePersistentHandle*>(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 |