| // Copyright (c) 2015, 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. | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| #include "vm/program_visitor.h" | |
| #include "vm/canonical_tables.h" | |
| #include "vm/closure_functions_cache.h" | |
| #include "vm/code_patcher.h" | |
| #include "vm/deopt_instructions.h" | |
| #include "vm/hash_map.h" | |
| #include "vm/object.h" | |
| #include "vm/object_store.h" | |
| #include "vm/symbols.h" | |
| namespace dart { | |
| class WorklistElement : public ZoneObject { | |
| public: | |
| WorklistElement(Zone* zone, const Object& object) | |
| : object_(Object::Handle(zone, object.ptr())), next_(nullptr) {} | |
| ObjectPtr value() const { return object_.ptr(); } | |
| void set_next(WorklistElement* elem) { next_ = elem; } | |
| WorklistElement* next() const { return next_; } | |
| private: | |
| const Object& object_; | |
| WorklistElement* next_; | |
| DISALLOW_COPY_AND_ASSIGN(WorklistElement); | |
| }; | |
| // Implements a FIFO queue, using IsEmpty, Add, Remove operations. | |
| class Worklist : public ValueObject { | |
| public: | |
| explicit Worklist(Zone* zone) | |
| : zone_(zone), first_(nullptr), last_(nullptr) {} | |
| bool IsEmpty() const { return first_ == nullptr; } | |
| void Add(const Object& value) { | |
| auto element = new (zone_) WorklistElement(zone_, value); | |
| if (first_ == nullptr) { | |
| first_ = element; | |
| ASSERT(last_ == nullptr); | |
| } else { | |
| ASSERT(last_ != nullptr); | |
| last_->set_next(element); | |
| } | |
| last_ = element; | |
| ASSERT(first_ != nullptr && last_ != nullptr); | |
| } | |
| ObjectPtr Remove() { | |
| ASSERT(first_ != nullptr); | |
| WorklistElement* result = first_; | |
| first_ = first_->next(); | |
| if (first_ == nullptr) { | |
| last_ = nullptr; | |
| } | |
| return result->value(); | |
| } | |
| private: | |
| Zone* const zone_; | |
| WorklistElement* first_; | |
| WorklistElement* last_; | |
| DISALLOW_COPY_AND_ASSIGN(Worklist); | |
| }; | |
| // Walks through the classes, functions, and code for the current program. | |
| // | |
| // Uses the heap object ID table to determine whether or not a given object | |
| // has been visited already. | |
| class ProgramWalker : public ValueObject { | |
| public: | |
| ProgramWalker(Zone* zone, Heap* heap, ClassVisitor* visitor) | |
| : heap_(heap), | |
| visitor_(visitor), | |
| worklist_(zone), | |
| class_object_(Object::Handle(zone)), | |
| class_fields_(Array::Handle(zone)), | |
| class_field_(Field::Handle(zone)), | |
| class_functions_(Array::Handle(zone)), | |
| class_function_(Function::Handle(zone)), | |
| class_code_(Code::Handle(zone)), | |
| function_code_(Code::Handle(zone)), | |
| static_calls_array_(Array::Handle(zone)), | |
| static_calls_table_entry_(Object::Handle(zone)), | |
| worklist_entry_(Object::Handle(zone)) {} | |
| ~ProgramWalker() { heap_->ResetObjectIdTable(); } | |
| // Adds the given object to the worklist if it's an object type that the | |
| // visitor can visit. | |
| void AddToWorklist(const Object& object) { | |
| // We don't visit null or non-heap objects. | |
| if (object.IsNull() || object.IsSmi()) return; | |
| // Check and set visited, even if we don't end up adding this to the list. | |
| if (heap_->GetObjectId(object.ptr()) != 0) return; | |
| heap_->SetObjectId(object.ptr(), 1); | |
| if (object.IsClass() || | |
| (object.IsFunction() && visitor_->IsFunctionVisitor()) || | |
| (object.IsCode() && visitor_->IsCodeVisitor())) { | |
| worklist_.Add(object); | |
| } | |
| } | |
| void VisitWorklist() { | |
| while (!worklist_.IsEmpty()) { | |
| worklist_entry_ = worklist_.Remove(); | |
| if (worklist_entry_.IsClass()) { | |
| VisitClass(Class::Cast(worklist_entry_)); | |
| } else if (worklist_entry_.IsFunction()) { | |
| VisitFunction(Function::Cast(worklist_entry_)); | |
| } else if (worklist_entry_.IsCode()) { | |
| VisitCode(Code::Cast(worklist_entry_)); | |
| } else { | |
| FATAL("Got unexpected object %s", worklist_entry_.ToCString()); | |
| } | |
| } | |
| } | |
| private: | |
| void VisitClass(const Class& cls) { | |
| visitor_->VisitClass(cls); | |
| if (!visitor_->IsFunctionVisitor()) return; | |
| class_functions_ = cls.current_functions(); | |
| for (intptr_t j = 0; j < class_functions_.Length(); j++) { | |
| class_function_ ^= class_functions_.At(j); | |
| AddToWorklist(class_function_); | |
| if (class_function_.HasImplicitClosureFunction()) { | |
| class_function_ = class_function_.ImplicitClosureFunction(); | |
| AddToWorklist(class_function_); | |
| } | |
| } | |
| class_functions_ = cls.invocation_dispatcher_cache(); | |
| for (intptr_t j = 0; j < class_functions_.Length(); j++) { | |
| class_object_ = class_functions_.At(j); | |
| if (class_object_.IsFunction()) { | |
| class_function_ ^= class_functions_.At(j); | |
| AddToWorklist(class_function_); | |
| } | |
| } | |
| class_fields_ = cls.fields(); | |
| for (intptr_t j = 0; j < class_fields_.Length(); j++) { | |
| class_field_ ^= class_fields_.At(j); | |
| if (class_field_.HasInitializerFunction()) { | |
| class_function_ = class_field_.InitializerFunction(); | |
| AddToWorklist(class_function_); | |
| } | |
| } | |
| if (!visitor_->IsCodeVisitor()) return; | |
| class_code_ = cls.allocation_stub(); | |
| if (!class_code_.IsNull()) AddToWorklist(class_code_); | |
| } | |
| void VisitFunction(const Function& function) { | |
| ASSERT(visitor_->IsFunctionVisitor()); | |
| visitor_->AsFunctionVisitor()->VisitFunction(function); | |
| if (!visitor_->IsCodeVisitor() || !function.HasCode()) return; | |
| function_code_ = function.CurrentCode(); | |
| AddToWorklist(function_code_); | |
| } | |
| void VisitCode(const Code& code) { | |
| ASSERT(visitor_->IsCodeVisitor()); | |
| visitor_->AsCodeVisitor()->VisitCode(code); | |
| // In the precompiler, some entries in the static calls table may need | |
| // to be visited as they may not be reachable from other sources. | |
| // | |
| // TODO(dartbug.com/41636): Figure out why walking the static calls table | |
| // in JIT mode with the DedupInstructions visitor fails, so we can remove | |
| // the check for AOT mode. | |
| static_calls_array_ = code.static_calls_target_table(); | |
| if (FLAG_precompiled_mode && !static_calls_array_.IsNull()) { | |
| StaticCallsTable static_calls(static_calls_array_); | |
| for (auto& view : static_calls) { | |
| static_calls_table_entry_ = | |
| view.Get<Code::kSCallTableCodeOrTypeTarget>(); | |
| if (static_calls_table_entry_.IsCode()) { | |
| AddToWorklist(Code::Cast(static_calls_table_entry_)); | |
| } | |
| } | |
| } | |
| } | |
| Heap* const heap_; | |
| ClassVisitor* const visitor_; | |
| Worklist worklist_; | |
| Object& class_object_; | |
| Array& class_fields_; | |
| Field& class_field_; | |
| Array& class_functions_; | |
| Function& class_function_; | |
| Code& class_code_; | |
| Code& function_code_; | |
| Array& static_calls_array_; | |
| Object& static_calls_table_entry_; | |
| Object& worklist_entry_; | |
| }; | |
| void ProgramVisitor::WalkProgram(Zone* zone, | |
| IsolateGroup* isolate_group, | |
| ClassVisitor* visitor) { | |
| auto const object_store = isolate_group->object_store(); | |
| auto const heap = isolate_group->heap(); | |
| ProgramWalker walker(zone, heap, visitor); | |
| // Walk through the libraries looking for visitable objects. | |
| const auto& libraries = | |
| GrowableObjectArray::Handle(zone, object_store->libraries()); | |
| auto& lib = Library::Handle(zone); | |
| auto& cls = Class::Handle(zone); | |
| for (intptr_t i = 0; i < libraries.Length(); i++) { | |
| lib ^= libraries.At(i); | |
| ClassDictionaryIterator it(lib, ClassDictionaryIterator::kIteratePrivate); | |
| while (it.HasNext()) { | |
| cls = it.GetNextClass(); | |
| walker.AddToWorklist(cls); | |
| } | |
| } | |
| // If there's a global object pool, add any visitable objects. | |
| const auto& global_object_pool = | |
| ObjectPool::Handle(zone, object_store->global_object_pool()); | |
| if (!global_object_pool.IsNull()) { | |
| auto& object = Object::Handle(zone); | |
| for (intptr_t i = 0; i < global_object_pool.Length(); i++) { | |
| auto const type = global_object_pool.TypeAt(i); | |
| if (type != ObjectPool::EntryType::kTaggedObject) continue; | |
| object = global_object_pool.ObjectAt(i); | |
| walker.AddToWorklist(object); | |
| } | |
| } | |
| if (visitor->IsFunctionVisitor()) { | |
| // Function objects not necessarily reachable from classes. | |
| ClosureFunctionsCache::ForAllClosureFunctions([&](const Function& fun) { | |
| walker.AddToWorklist(fun); | |
| ASSERT(!fun.HasImplicitClosureFunction()); | |
| return true; // Continue iteration. | |
| }); | |
| // TODO(dartbug.com/43049): Use a more general solution and remove manual | |
| // tracking through object_store->ffi_callback_functions. | |
| if (object_store->ffi_callback_functions() != Array::null()) { | |
| auto& function = Function::Handle(zone); | |
| FfiCallbackFunctionSet set(object_store->ffi_callback_functions()); | |
| FfiCallbackFunctionSet::Iterator it(&set); | |
| while (it.MoveNext()) { | |
| const intptr_t entry = it.Current(); | |
| function ^= set.GetKey(entry); | |
| walker.AddToWorklist(function); | |
| } | |
| set.Release(); | |
| } | |
| } | |
| if (visitor->IsCodeVisitor()) { | |
| // Code objects not necessarily reachable from functions. | |
| auto& code = Code::Handle(zone); | |
| const auto& dispatch_table_entries = | |
| Array::Handle(zone, object_store->dispatch_table_code_entries()); | |
| if (!dispatch_table_entries.IsNull()) { | |
| for (intptr_t i = 0; i < dispatch_table_entries.Length(); i++) { | |
| code ^= dispatch_table_entries.At(i); | |
| walker.AddToWorklist(code); | |
| } | |
| } | |
| } | |
| // Walk the program starting from any roots we added to the worklist. | |
| walker.VisitWorklist(); | |
| } | |
| // A base class for deduplication of objects. T is the type of canonical objects | |
| // being stored, whereas S is a trait appropriate for a DirectChainedHashMap | |
| // based set containing those canonical objects. | |
| template <typename T, typename S> | |
| class Deduper : public ValueObject { | |
| public: | |
| explicit Deduper(Zone* zone) : zone_(zone), canonical_objects_(zone) {} | |
| virtual ~Deduper() {} | |
| protected: | |
| // Predicate for objects of type T. Must be overridden for class hierarchies | |
| // like Instance and AbstractType, as it defaults to class ID comparison. | |
| virtual bool IsCorrectType(const Object& obj) const { | |
| return obj.GetClassId() == T::kClassId; | |
| } | |
| // Predicate for choosing Ts to canonicalize. | |
| virtual bool CanCanonicalize(const T& t) const { return true; } | |
| // Predicate for objects that are okay to add to the canonical hash set. | |
| // Override IsCorrectType and/or CanCanonicalize to change the behavior. | |
| bool ShouldAdd(const Object& obj) const { | |
| return !obj.IsNull() && IsCorrectType(obj) && CanCanonicalize(T::Cast(obj)); | |
| } | |
| void AddCanonical(const T& obj) { | |
| if (!ShouldAdd(obj)) return; | |
| ASSERT(!canonical_objects_.HasKey(&obj)); | |
| canonical_objects_.Insert(&T::ZoneHandle(zone_, obj.ptr())); | |
| } | |
| typename T::ObjectPtrType Dedup(const T& obj) { | |
| if (ShouldAdd(obj)) { | |
| if (auto const canonical = canonical_objects_.LookupValue(&obj)) { | |
| return canonical->ptr(); | |
| } | |
| AddCanonical(obj); | |
| } | |
| return obj.ptr(); | |
| } | |
| Zone* const zone_; | |
| DirectChainedHashMap<S> canonical_objects_; | |
| }; | |
| void ProgramVisitor::BindStaticCalls(Thread* thread) { | |
| class BindStaticCallsVisitor : public CodeVisitor { | |
| public: | |
| explicit BindStaticCallsVisitor(Zone* zone) | |
| : table_(Array::Handle(zone)), | |
| kind_and_offset_(Smi::Handle(zone)), | |
| target_(Object::Handle(zone)), | |
| target_code_(Code::Handle(zone)) {} | |
| void VisitCode(const Code& code) { | |
| table_ = code.static_calls_target_table(); | |
| if (table_.IsNull()) return; | |
| StaticCallsTable static_calls(table_); | |
| // We can only remove the target table in precompiled mode, since more | |
| // calls may be added later otherwise. | |
| bool only_call_via_code = FLAG_precompiled_mode; | |
| for (const auto& view : static_calls) { | |
| kind_and_offset_ = view.Get<Code::kSCallTableKindAndOffset>(); | |
| auto const kind = Code::KindField::decode(kind_and_offset_.Value()); | |
| if (kind != Code::kCallViaCode) { | |
| ASSERT(kind == Code::kPcRelativeCall || | |
| kind == Code::kPcRelativeTailCall || | |
| kind == Code::kPcRelativeTTSCall); | |
| only_call_via_code = false; | |
| continue; | |
| } | |
| target_ = view.Get<Code::kSCallTableFunctionTarget>(); | |
| if (target_.IsNull()) { | |
| target_ = | |
| Code::RawCast(view.Get<Code::kSCallTableCodeOrTypeTarget>()); | |
| ASSERT(!target_.IsNull()); // Already bound. | |
| continue; | |
| } | |
| auto const pc_offset = | |
| Code::OffsetField::decode(kind_and_offset_.Value()); | |
| const uword pc = pc_offset + code.PayloadStart(); | |
| // In JIT mode, static calls initially call the CallStaticFunction stub | |
| // because their target might not be compiled yet. If the target has | |
| // been compiled by this point, we patch the call to call the target | |
| // directly. | |
| // | |
| // In precompiled mode, the binder runs after tree shaking, during which | |
| // all targets have been compiled, and so the binder replaces all static | |
| // calls with direct calls to the target. | |
| // | |
| // Cf. runtime entry PatchStaticCall called from CallStaticFunction | |
| // stub. | |
| const auto& fun = Function::Cast(target_); | |
| ASSERT(!FLAG_precompiled_mode || fun.HasCode()); | |
| target_code_ = fun.HasCode() ? fun.CurrentCode() | |
| : StubCode::CallStaticFunction().ptr(); | |
| CodePatcher::PatchStaticCallAt(pc, code, target_code_); | |
| } | |
| if (only_call_via_code) { | |
| ASSERT(FLAG_precompiled_mode); | |
| // In precompiled mode, the Dart runtime won't patch static calls | |
| // anymore, so drop the static call table to save space. | |
| // Note: it is okay to drop the table fully even when generating | |
| // V8 snapshot profile because code objects are linked through the | |
| // pool. | |
| code.set_static_calls_target_table(Object::empty_array()); | |
| } | |
| } | |
| private: | |
| Array& table_; | |
| Smi& kind_and_offset_; | |
| Object& target_; | |
| Code& target_code_; | |
| }; | |
| StackZone stack_zone(thread); | |
| BindStaticCallsVisitor visitor(thread->zone()); | |
| WalkProgram(thread->zone(), thread->isolate_group(), &visitor); | |
| } | |
| DECLARE_FLAG(charp, trace_precompiler_to); | |
| DECLARE_FLAG(charp, write_v8_snapshot_profile_to); | |
| void ProgramVisitor::ShareMegamorphicBuckets(Thread* thread) { | |
| StackZone stack_zone(thread); | |
| Zone* zone = thread->zone(); | |
| const GrowableObjectArray& table = GrowableObjectArray::Handle( | |
| zone, thread->isolate_group()->object_store()->megamorphic_cache_table()); | |
| if (table.IsNull()) return; | |
| MegamorphicCache& cache = MegamorphicCache::Handle(zone); | |
| const intptr_t capacity = 1; | |
| const Array& buckets = Array::Handle( | |
| zone, Array::New(MegamorphicCache::kEntryLength * capacity, Heap::kOld)); | |
| const Function& handler = Function::Handle(zone); | |
| MegamorphicCache::SetEntry(buckets, 0, Object::smi_illegal_cid(), handler); | |
| for (intptr_t i = 0; i < table.Length(); i++) { | |
| cache ^= table.At(i); | |
| cache.set_buckets(buckets); | |
| cache.set_mask(capacity - 1); | |
| cache.set_filled_entry_count(0); | |
| } | |
| } | |
| class StackMapEntry : public ZoneObject { | |
| public: | |
| StackMapEntry(Zone* zone, | |
| const CompressedStackMaps::Iterator<CompressedStackMaps>& it) | |
| : maps_(CompressedStackMaps::Handle(zone, it.maps_.ptr())), | |
| bits_container_( | |
| CompressedStackMaps::Handle(zone, it.bits_container_.ptr())), | |
| // If the map uses the global table, this accessor call ensures the | |
| // entry is fully loaded before we retrieve [it.current_bits_offset_]. | |
| spill_slot_bit_count_(it.SpillSlotBitCount()), | |
| non_spill_slot_bit_count_(it.Length() - it.SpillSlotBitCount()), | |
| bits_offset_(it.current_bits_offset_) { | |
| ASSERT(!maps_.IsNull() && !maps_.IsGlobalTable()); | |
| ASSERT(!bits_container_.IsNull()); | |
| ASSERT(!maps_.UsesGlobalTable() || bits_container_.IsGlobalTable()); | |
| ASSERT(it.current_spill_slot_bit_count_ >= 0); | |
| } | |
| static constexpr intptr_t kHashBits = Object::kHashBits; | |
| uword Hash() { | |
| if (hash_ != 0) return hash_; | |
| uint32_t hash = 0; | |
| hash = CombineHashes(hash, spill_slot_bit_count_); | |
| hash = CombineHashes(hash, non_spill_slot_bit_count_); | |
| { | |
| NoSafepointScope scope; | |
| auto const start = PayloadData(); | |
| auto const end = start + PayloadLength(); | |
| for (auto cursor = start; cursor < end; cursor++) { | |
| hash = CombineHashes(hash, *cursor); | |
| } | |
| } | |
| hash_ = FinalizeHash(hash, kHashBits); | |
| return hash_; | |
| } | |
| bool Equals(const StackMapEntry& other) const { | |
| if (spill_slot_bit_count_ != other.spill_slot_bit_count_ || | |
| non_spill_slot_bit_count_ != other.non_spill_slot_bit_count_) { | |
| return false; | |
| } | |
| // Since we ensure that bits in the payload that are not part of the | |
| // actual stackmap data are cleared, we can just compare payloads by byte | |
| // instead of calling IsObject for each bit. | |
| NoSafepointScope scope; | |
| return memcmp(PayloadData(), other.PayloadData(), PayloadLength()) == 0; | |
| } | |
| // Encodes this StackMapEntry to the given array of bytes and returns the | |
| // initial offset of the entry in the array. | |
| intptr_t EncodeTo(NonStreamingWriteStream* stream) { | |
| auto const current_offset = stream->Position(); | |
| stream->WriteLEB128(spill_slot_bit_count_); | |
| stream->WriteLEB128(non_spill_slot_bit_count_); | |
| { | |
| NoSafepointScope scope; | |
| stream->WriteBytes(PayloadData(), PayloadLength()); | |
| } | |
| return current_offset; | |
| } | |
| intptr_t UsageCount() const { return uses_; } | |
| void IncrementUsageCount() { uses_ += 1; } | |
| private: | |
| intptr_t Length() const { | |
| return spill_slot_bit_count_ + non_spill_slot_bit_count_; | |
| } | |
| intptr_t PayloadLength() const { | |
| return Utils::RoundUp(Length(), kBitsPerByte) >> kBitsPerByteLog2; | |
| } | |
| const uint8_t* PayloadData() const { | |
| return bits_container_.ptr()->untag()->payload()->data() + bits_offset_; | |
| } | |
| const CompressedStackMaps& maps_; | |
| const CompressedStackMaps& bits_container_; | |
| const intptr_t spill_slot_bit_count_; | |
| const intptr_t non_spill_slot_bit_count_; | |
| const intptr_t bits_offset_; | |
| intptr_t uses_ = 1; | |
| intptr_t hash_ = 0; | |
| }; | |
| // Used for maps of indices and offsets. These are non-negative, and so the | |
| // value for entries may be 0. Since 0 is kNoValue for | |
| // RawPointerKeyValueTrait<const StackMapEntry, intptr_t>, we can't just use it. | |
| class StackMapEntryKeyIntValueTrait { | |
| public: | |
| typedef StackMapEntry* Key; | |
| typedef intptr_t Value; | |
| struct Pair { | |
| Key key; | |
| Value value; | |
| Pair() : key(nullptr), value(-1) {} | |
| Pair(const Key key, const Value& value) | |
| : key(ASSERT_NOTNULL(key)), value(value) {} | |
| Pair(const Pair& other) : key(other.key), value(other.value) {} | |
| Pair& operator=(const Pair&) = default; | |
| }; | |
| static Key KeyOf(Pair kv) { return kv.key; } | |
| static Value ValueOf(Pair kv) { return kv.value; } | |
| static uword Hash(Key key) { return key->Hash(); } | |
| static bool IsKeyEqual(Pair kv, Key key) { return key->Equals(*kv.key); } | |
| }; | |
| typedef DirectChainedHashMap<StackMapEntryKeyIntValueTrait> StackMapEntryIntMap; | |
| void ProgramVisitor::NormalizeAndDedupCompressedStackMaps(Thread* thread) { | |
| // Walks all the CSMs in Code objects and collects their entry information | |
| // for consolidation. | |
| class CollectStackMapEntriesVisitor : public CodeVisitor { | |
| public: | |
| CollectStackMapEntriesVisitor(Zone* zone, | |
| const CompressedStackMaps& global_table) | |
| : zone_(zone), | |
| old_global_table_(global_table), | |
| compressed_stackmaps_(CompressedStackMaps::Handle(zone)), | |
| collected_entries_(zone, 2), | |
| entry_indices_(zone), | |
| entry_offset_(zone) { | |
| ASSERT(old_global_table_.IsNull() || old_global_table_.IsGlobalTable()); | |
| } | |
| void VisitCode(const Code& code) { | |
| compressed_stackmaps_ = code.compressed_stackmaps(); | |
| CompressedStackMaps::Iterator<CompressedStackMaps> it( | |
| compressed_stackmaps_, old_global_table_); | |
| while (it.MoveNext()) { | |
| auto const entry = new (zone_) StackMapEntry(zone_, it); | |
| auto const index = entry_indices_.LookupValue(entry); | |
| if (index < 0) { | |
| auto new_index = collected_entries_.length(); | |
| collected_entries_.Add(entry); | |
| entry_indices_.Insert({entry, new_index}); | |
| } else { | |
| collected_entries_.At(index)->IncrementUsageCount(); | |
| } | |
| } | |
| } | |
| // Creates a new global table of stack map information. Also adds the | |
| // offsets of encoded StackMapEntry objects to entry_offsets for use | |
| // when normalizing CompressedStackMaps. | |
| CompressedStackMapsPtr CreateGlobalTable( | |
| StackMapEntryIntMap* entry_offsets) { | |
| ASSERT(entry_offsets->IsEmpty()); | |
| if (collected_entries_.length() == 0) { | |
| return CompressedStackMaps::null(); | |
| } | |
| // First, sort the entries from most used to least used. This way, | |
| // the most often used CSMs will have the lowest offsets, which means | |
| // they will be smaller when LEB128 encoded. | |
| collected_entries_.Sort( | |
| [](StackMapEntry* const* e1, StackMapEntry* const* e2) { | |
| return static_cast<int>((*e2)->UsageCount() - (*e1)->UsageCount()); | |
| }); | |
| MallocWriteStream stream(128); | |
| // Encode the entries and record their offset in the payload. Sorting the | |
| // entries may have changed their indices, so update those as well. | |
| for (intptr_t i = 0, n = collected_entries_.length(); i < n; i++) { | |
| auto const entry = collected_entries_.At(i); | |
| entry_indices_.Update({entry, i}); | |
| entry_offsets->Insert({entry, entry->EncodeTo(&stream)}); | |
| } | |
| const auto& data = CompressedStackMaps::Handle( | |
| zone_, CompressedStackMaps::NewGlobalTable(stream.buffer(), | |
| stream.bytes_written())); | |
| return data.ptr(); | |
| } | |
| private: | |
| Zone* const zone_; | |
| const CompressedStackMaps& old_global_table_; | |
| CompressedStackMaps& compressed_stackmaps_; | |
| GrowableArray<StackMapEntry*> collected_entries_; | |
| StackMapEntryIntMap entry_indices_; | |
| StackMapEntryIntMap entry_offset_; | |
| }; | |
| // Walks all the CSMs in Code objects, normalizes them, and then dedups them. | |
| // | |
| // We use normalized to refer to CSMs whose entries are references to the | |
| // new global table created during stack map collection, and non-normalized | |
| // for CSMs that either have inlined entry information or whose entries are | |
| // references to the _old_ global table in the object store, if any. | |
| class NormalizeAndDedupCompressedStackMapsVisitor | |
| : public CodeVisitor, | |
| public Deduper<CompressedStackMaps, | |
| PointerSetKeyValueTrait<const CompressedStackMaps>> { | |
| public: | |
| NormalizeAndDedupCompressedStackMapsVisitor(Zone* zone, | |
| IsolateGroup* isolate_group) | |
| : Deduper(zone), | |
| old_global_table_(CompressedStackMaps::Handle( | |
| zone, | |
| isolate_group->object_store() | |
| ->canonicalized_stack_map_entries())), | |
| entry_offsets_(zone), | |
| maps_(CompressedStackMaps::Handle(zone)) { | |
| ASSERT(old_global_table_.IsNull() || old_global_table_.IsGlobalTable()); | |
| // The stack map normalization and deduplication happens in two phases: | |
| // | |
| // 1) Visit all CompressedStackMaps (CSM) objects and collect individual | |
| // entry info as canonicalized StackMapEntries (SMEs). Also record the | |
| // frequency the same entry info was seen across all CSMs in each SME. | |
| CollectStackMapEntriesVisitor collect_visitor(zone, old_global_table_); | |
| WalkProgram(zone, isolate_group, &collect_visitor); | |
| // The results of phase 1 are used to create a new global table with | |
| // entries sorted by decreasing frequency, so that entries that appear | |
| // more often in CSMs have smaller payload offsets (less bytes used in | |
| // the LEB128 encoding). The new global table is put into place | |
| // immediately, as we already have a handle on the old table. | |
| const auto& new_global_table = CompressedStackMaps::Handle( | |
| zone, collect_visitor.CreateGlobalTable(&entry_offsets_)); | |
| isolate_group->object_store()->set_canonicalized_stack_map_entries( | |
| new_global_table); | |
| // 2) Visit all CSMs and replace each with a canonicalized normalized | |
| // version that uses the new global table for non-PC offset entry | |
| // information. This part is done in VisitCode. | |
| } | |
| void VisitCode(const Code& code) { | |
| maps_ = code.compressed_stackmaps(); | |
| if (maps_.IsNull()) return; | |
| // First check is to make sure [maps] hasn't already been normalized, | |
| // since any normalized map already has a canonical entry in the set. | |
| if (auto const canonical = canonical_objects_.LookupValue(&maps_)) { | |
| maps_ = canonical->ptr(); | |
| } else { | |
| maps_ = NormalizeEntries(maps_); | |
| maps_ = Dedup(maps_); | |
| } | |
| code.set_compressed_stackmaps(maps_); | |
| } | |
| private: | |
| // Creates a normalized CSM from the given non-normalized CSM. | |
| CompressedStackMapsPtr NormalizeEntries(const CompressedStackMaps& maps) { | |
| if (maps.payload_size() == 0) { | |
| // No entries, so use the canonical empty map. | |
| return Object::empty_compressed_stackmaps().ptr(); | |
| } | |
| MallocWriteStream new_payload(maps.payload_size()); | |
| CompressedStackMaps::Iterator<CompressedStackMaps> it(maps, | |
| old_global_table_); | |
| intptr_t last_offset = 0; | |
| while (it.MoveNext()) { | |
| StackMapEntry entry(zone_, it); | |
| const intptr_t entry_offset = entry_offsets_.LookupValue(&entry); | |
| const intptr_t pc_delta = it.pc_offset() - last_offset; | |
| new_payload.WriteLEB128(pc_delta); | |
| new_payload.WriteLEB128(entry_offset); | |
| last_offset = it.pc_offset(); | |
| } | |
| return CompressedStackMaps::NewUsingTable(new_payload.buffer(), | |
| new_payload.bytes_written()); | |
| } | |
| const CompressedStackMaps& old_global_table_; | |
| StackMapEntryIntMap entry_offsets_; | |
| CompressedStackMaps& maps_; | |
| }; | |
| StackZone stack_zone(thread); | |
| NormalizeAndDedupCompressedStackMapsVisitor visitor(thread->zone(), | |
| thread->isolate_group()); | |
| WalkProgram(thread->zone(), thread->isolate_group(), &visitor); | |
| } | |
| class PcDescriptorsKeyValueTrait { | |
| public: | |
| // Typedefs needed for the DirectChainedHashMap template. | |
| typedef const PcDescriptors* Key; | |
| typedef const PcDescriptors* Value; | |
| typedef const PcDescriptors* Pair; | |
| static Key KeyOf(Pair kv) { return kv; } | |
| static Value ValueOf(Pair kv) { return kv; } | |
| static inline uword Hash(Key key) { return Utils::WordHash(key->Length()); } | |
| static inline bool IsKeyEqual(Pair pair, Key key) { | |
| return pair->Equals(*key); | |
| } | |
| }; | |
| void ProgramVisitor::DedupPcDescriptors(Thread* thread) { | |
| class DedupPcDescriptorsVisitor | |
| : public CodeVisitor, | |
| public Deduper<PcDescriptors, PcDescriptorsKeyValueTrait> { | |
| public: | |
| explicit DedupPcDescriptorsVisitor(Zone* zone) | |
| : Deduper(zone), pc_descriptor_(PcDescriptors::Handle(zone)) {} | |
| void VisitCode(const Code& code) { | |
| pc_descriptor_ = code.pc_descriptors(); | |
| pc_descriptor_ = Dedup(pc_descriptor_); | |
| code.set_pc_descriptors(pc_descriptor_); | |
| } | |
| private: | |
| PcDescriptors& pc_descriptor_; | |
| }; | |
| StackZone stack_zone(thread); | |
| DedupPcDescriptorsVisitor visitor(thread->zone()); | |
| WalkProgram(thread->zone(), thread->isolate_group(), &visitor); | |
| } | |
| class TypedDataKeyValueTrait { | |
| public: | |
| // Typedefs needed for the DirectChainedHashMap template. | |
| typedef const TypedData* Key; | |
| typedef const TypedData* Value; | |
| typedef const TypedData* Pair; | |
| static Key KeyOf(Pair kv) { return kv; } | |
| static Value ValueOf(Pair kv) { return kv; } | |
| static inline uword Hash(Key key) { return key->CanonicalizeHash(); } | |
| static inline bool IsKeyEqual(Pair pair, Key key) { | |
| return pair->CanonicalizeEquals(*key); | |
| } | |
| }; | |
| class TypedDataDeduper : public Deduper<TypedData, TypedDataKeyValueTrait> { | |
| public: | |
| explicit TypedDataDeduper(Zone* zone) : Deduper(zone) {} | |
| private: | |
| bool IsCorrectType(const Object& obj) const { return obj.IsTypedData(); } | |
| }; | |
| void ProgramVisitor::DedupDeoptEntries(Thread* thread) { | |
| class DedupDeoptEntriesVisitor : public CodeVisitor, public TypedDataDeduper { | |
| public: | |
| explicit DedupDeoptEntriesVisitor(Zone* zone) | |
| : TypedDataDeduper(zone), | |
| deopt_table_(Array::Handle(zone)), | |
| deopt_entry_(TypedData::Handle(zone)), | |
| offset_(Smi::Handle(zone)), | |
| reason_and_flags_(Smi::Handle(zone)) {} | |
| void VisitCode(const Code& code) { | |
| deopt_table_ = code.deopt_info_array(); | |
| if (deopt_table_.IsNull()) return; | |
| intptr_t length = DeoptTable::GetLength(deopt_table_); | |
| for (intptr_t i = 0; i < length; i++) { | |
| DeoptTable::GetEntry(deopt_table_, i, &offset_, &deopt_entry_, | |
| &reason_and_flags_); | |
| ASSERT(!deopt_entry_.IsNull()); | |
| deopt_entry_ = Dedup(deopt_entry_); | |
| ASSERT(!deopt_entry_.IsNull()); | |
| DeoptTable::SetEntry(deopt_table_, i, offset_, deopt_entry_, | |
| reason_and_flags_); | |
| } | |
| } | |
| private: | |
| Array& deopt_table_; | |
| TypedData& deopt_entry_; | |
| Smi& offset_; | |
| Smi& reason_and_flags_; | |
| }; | |
| if (FLAG_precompiled_mode) return; | |
| StackZone stack_zone(thread); | |
| DedupDeoptEntriesVisitor visitor(thread->zone()); | |
| WalkProgram(thread->zone(), thread->isolate_group(), &visitor); | |
| } | |
| #if defined(DART_PRECOMPILER) | |
| void ProgramVisitor::DedupCatchEntryMovesMaps(Thread* thread) { | |
| class DedupCatchEntryMovesMapsVisitor : public CodeVisitor, | |
| public TypedDataDeduper { | |
| public: | |
| explicit DedupCatchEntryMovesMapsVisitor(Zone* zone) | |
| : TypedDataDeduper(zone), | |
| catch_entry_moves_maps_(TypedData::Handle(zone)) {} | |
| void VisitCode(const Code& code) { | |
| catch_entry_moves_maps_ = code.catch_entry_moves_maps(); | |
| catch_entry_moves_maps_ = Dedup(catch_entry_moves_maps_); | |
| code.set_catch_entry_moves_maps(catch_entry_moves_maps_); | |
| } | |
| private: | |
| TypedData& catch_entry_moves_maps_; | |
| }; | |
| if (!FLAG_precompiled_mode) return; | |
| StackZone stack_zone(thread); | |
| DedupCatchEntryMovesMapsVisitor visitor(thread->zone()); | |
| WalkProgram(thread->zone(), thread->isolate_group(), &visitor); | |
| } | |
| class UnlinkedCallKeyValueTrait { | |
| public: | |
| // Typedefs needed for the DirectChainedHashMap template. | |
| typedef const UnlinkedCall* Key; | |
| typedef const UnlinkedCall* Value; | |
| typedef const UnlinkedCall* Pair; | |
| static Key KeyOf(Pair kv) { return kv; } | |
| static Value ValueOf(Pair kv) { return kv; } | |
| static inline uword Hash(Key key) { return key->Hash(); } | |
| static inline bool IsKeyEqual(Pair pair, Key key) { | |
| return pair->Equals(*key); | |
| } | |
| }; | |
| void ProgramVisitor::DedupUnlinkedCalls(Thread* thread) { | |
| class DedupUnlinkedCallsVisitor | |
| : public CodeVisitor, | |
| public Deduper<UnlinkedCall, UnlinkedCallKeyValueTrait> { | |
| public: | |
| explicit DedupUnlinkedCallsVisitor(Zone* zone, IsolateGroup* isolate_group) | |
| : Deduper(zone), | |
| entry_(Object::Handle(zone)), | |
| pool_(ObjectPool::Handle(zone)) { | |
| auto& gop = ObjectPool::Handle( | |
| zone, isolate_group->object_store()->global_object_pool()); | |
| ASSERT(!gop.IsNull()); | |
| DedupPool(gop); | |
| } | |
| void DedupPool(const ObjectPool& pool) { | |
| if (pool.IsNull()) return; | |
| for (intptr_t i = 0; i < pool.Length(); i++) { | |
| if (pool.TypeAt(i) != ObjectPool::EntryType::kTaggedObject) { | |
| continue; | |
| } | |
| entry_ = pool.ObjectAt(i); | |
| if (!entry_.IsUnlinkedCall()) continue; | |
| entry_ = Dedup(UnlinkedCall::Cast(entry_)); | |
| pool.SetObjectAt(i, entry_); | |
| } | |
| } | |
| void VisitCode(const Code& code) { | |
| pool_ = code.object_pool(); | |
| DedupPool(pool_); | |
| } | |
| private: | |
| Object& entry_; | |
| ObjectPool& pool_; | |
| }; | |
| if (!FLAG_precompiled_mode) return; | |
| StackZone stack_zone(thread); | |
| DedupUnlinkedCallsVisitor visitor(thread->zone(), thread->isolate_group()); | |
| // Deduplicate local object pools as they are used to trace | |
| // objects when writing snapshots. | |
| WalkProgram(thread->zone(), thread->isolate_group(), &visitor); | |
| } | |
| void ProgramVisitor::PruneSubclasses(Thread* thread) { | |
| class PruneSubclassesVisitor : public ClassVisitor { | |
| public: | |
| explicit PruneSubclassesVisitor(Zone* zone) | |
| : ClassVisitor(), | |
| old_implementors_(GrowableObjectArray::Handle(zone)), | |
| new_implementors_(GrowableObjectArray::Handle(zone)), | |
| implementor_(Class::Handle(zone)), | |
| old_subclasses_(GrowableObjectArray::Handle(zone)), | |
| new_subclasses_(GrowableObjectArray::Handle(zone)), | |
| subclass_(Class::Handle(zone)), | |
| null_list_(GrowableObjectArray::Handle(zone)) {} | |
| void VisitClass(const Class& klass) { | |
| old_implementors_ = klass.direct_implementors_unsafe(); | |
| if (!old_implementors_.IsNull()) { | |
| new_implementors_ = GrowableObjectArray::New(); | |
| for (intptr_t i = 0; i < old_implementors_.Length(); i++) { | |
| implementor_ ^= old_implementors_.At(i); | |
| if (implementor_.id() != kIllegalCid) { | |
| new_implementors_.Add(implementor_); | |
| } | |
| } | |
| if (new_implementors_.Length() == 0) { | |
| klass.set_direct_implementors(null_list_); | |
| } else { | |
| klass.set_direct_implementors(new_implementors_); | |
| } | |
| } | |
| old_subclasses_ = klass.direct_subclasses_unsafe(); | |
| if (!old_subclasses_.IsNull()) { | |
| new_subclasses_ = GrowableObjectArray::New(); | |
| for (intptr_t i = 0; i < old_subclasses_.Length(); i++) { | |
| subclass_ ^= old_subclasses_.At(i); | |
| if (subclass_.id() != kIllegalCid) { | |
| new_subclasses_.Add(subclass_); | |
| } | |
| } | |
| if (new_subclasses_.Length() == 0) { | |
| klass.set_direct_subclasses(null_list_); | |
| } else { | |
| klass.set_direct_subclasses(new_subclasses_); | |
| } | |
| } | |
| } | |
| private: | |
| GrowableObjectArray& old_implementors_; | |
| GrowableObjectArray& new_implementors_; | |
| Class& implementor_; | |
| GrowableObjectArray& old_subclasses_; | |
| GrowableObjectArray& new_subclasses_; | |
| Class& subclass_; | |
| GrowableObjectArray& null_list_; | |
| }; | |
| StackZone stack_zone(thread); | |
| PruneSubclassesVisitor visitor(thread->zone()); | |
| SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock()); | |
| WalkProgram(thread->zone(), thread->isolate_group(), &visitor); | |
| } | |
| #endif // defined(DART_PRECOMPILER) | |
| class CodeSourceMapKeyValueTrait { | |
| public: | |
| // Typedefs needed for the DirectChainedHashMap template. | |
| typedef const CodeSourceMap* Key; | |
| typedef const CodeSourceMap* Value; | |
| typedef const CodeSourceMap* Pair; | |
| static Key KeyOf(Pair kv) { return kv; } | |
| static Value ValueOf(Pair kv) { return kv; } | |
| static inline uword Hash(Key key) { | |
| ASSERT(!key->IsNull()); | |
| return Utils::WordHash(key->Hash()); | |
| } | |
| static inline bool IsKeyEqual(Pair pair, Key key) { | |
| ASSERT(!pair->IsNull() && !key->IsNull()); | |
| return pair->Equals(*key); | |
| } | |
| }; | |
| void ProgramVisitor::DedupCodeSourceMaps(Thread* thread) { | |
| class DedupCodeSourceMapsVisitor | |
| : public CodeVisitor, | |
| public Deduper<CodeSourceMap, CodeSourceMapKeyValueTrait> { | |
| public: | |
| explicit DedupCodeSourceMapsVisitor(Zone* zone) | |
| : Deduper(zone), code_source_map_(CodeSourceMap::Handle(zone)) {} | |
| void VisitCode(const Code& code) { | |
| code_source_map_ = code.code_source_map(); | |
| code_source_map_ = Dedup(code_source_map_); | |
| code.set_code_source_map(code_source_map_); | |
| } | |
| private: | |
| CodeSourceMap& code_source_map_; | |
| }; | |
| StackZone stack_zone(thread); | |
| DedupCodeSourceMapsVisitor visitor(thread->zone()); | |
| WalkProgram(thread->zone(), thread->isolate_group(), &visitor); | |
| } | |
| class ArrayKeyValueTrait { | |
| public: | |
| // Typedefs needed for the DirectChainedHashMap template. | |
| typedef const Array* Key; | |
| typedef const Array* Value; | |
| typedef const Array* Pair; | |
| static Key KeyOf(Pair kv) { return kv; } | |
| static Value ValueOf(Pair kv) { return kv; } | |
| static inline uword Hash(Key key) { | |
| ASSERT(!key->IsNull()); | |
| ASSERT(Thread::Current()->no_safepoint_scope_depth() > 0); | |
| const intptr_t len = key->Length(); | |
| uint32_t hash = Utils::WordHash(len); | |
| for (intptr_t i = 0; i < len; ++i) { | |
| hash = | |
| CombineHashes(hash, Utils::WordHash(static_cast<uword>(key->At(i)))); | |
| } | |
| return hash; | |
| } | |
| static inline bool IsKeyEqual(Pair pair, Key key) { | |
| ASSERT(!pair->IsNull() && !key->IsNull()); | |
| if (pair->Length() != key->Length()) return false; | |
| for (intptr_t i = 0; i < pair->Length(); i++) { | |
| if (pair->At(i) != key->At(i)) return false; | |
| } | |
| return true; | |
| } | |
| }; | |
| void ProgramVisitor::DedupLists(Thread* thread) { | |
| class DedupListsVisitor : public CodeVisitor, | |
| public Deduper<Array, ArrayKeyValueTrait> { | |
| public: | |
| explicit DedupListsVisitor(Zone* zone) | |
| : Deduper(zone), | |
| list_(Array::Handle(zone)), | |
| field_(Field::Handle(zone)) {} | |
| void VisitCode(const Code& code) { | |
| if (!code.IsFunctionCode()) return; | |
| list_ = code.inlined_id_to_function(); | |
| list_ = Dedup(list_); | |
| code.set_inlined_id_to_function(list_); | |
| list_ = code.deopt_info_array(); | |
| list_ = Dedup(list_); | |
| code.set_deopt_info_array(list_); | |
| list_ = code.static_calls_target_table(); | |
| list_ = Dedup(list_); | |
| code.set_static_calls_target_table(list_); | |
| } | |
| void VisitFunction(const Function& function) { | |
| // Don't bother deduping the positional names in precompiled mode, as | |
| // they'll be dropped anyway. | |
| if (!FLAG_precompiled_mode) { | |
| list_ = function.positional_parameter_names(); | |
| if (!list_.IsNull()) { | |
| list_ = Dedup(list_); | |
| function.set_positional_parameter_names(list_); | |
| } | |
| } | |
| } | |
| private: | |
| bool IsCorrectType(const Object& obj) const { return obj.IsArray(); } | |
| Array& list_; | |
| Field& field_; | |
| }; | |
| StackZone stack_zone(thread); | |
| // ArrayKeyValueTrait::Hash is based on object addresses, so make | |
| // sure GC doesn't happen and doesn't move objects. | |
| NoSafepointScope no_safepoint; | |
| DedupListsVisitor visitor(thread->zone()); | |
| WalkProgram(thread->zone(), thread->isolate_group(), &visitor); | |
| } | |
| // Traits for comparing two [Instructions] objects for equality, which is | |
| // implemented as bit-wise equality. | |
| // | |
| // This considers two instruction objects to be equal even if they have | |
| // different static call targets. Since the static call targets are called via | |
| // the object pool this is ok. | |
| class InstructionsKeyValueTrait { | |
| public: | |
| // Typedefs needed for the DirectChainedHashMap template. | |
| typedef const Instructions* Key; | |
| typedef const Instructions* Value; | |
| typedef const Instructions* Pair; | |
| static Key KeyOf(Pair kv) { return kv; } | |
| static Value ValueOf(Pair kv) { return kv; } | |
| static inline uword Hash(Key key) { return key->Hash(); } | |
| static inline bool IsKeyEqual(Pair pair, Key key) { | |
| return pair->Equals(*key); | |
| } | |
| }; | |
| // Traits for comparing two [Code] objects for equality. | |
| // | |
| // The instruction deduplication naturally causes us to have a one-to-many | |
| // relationship between Instructions and Code objects. | |
| // | |
| // In AOT frames only have PCs. However, the runtime needs e.g. stack maps from | |
| // the [Code] to scan such a frame. So we ensure that instructions of code | |
| // objects are only deduplicated if the metadata in the code is the same. | |
| // The runtime can then pick any code object corresponding to the PC in the | |
| // frame and use the metadata. | |
| #if defined(DART_PRECOMPILER) | |
| class CodeKeyValueTrait { | |
| public: | |
| // Typedefs needed for the DirectChainedHashMap template. | |
| typedef const Code* Key; | |
| typedef const Code* Value; | |
| typedef const Code* Pair; | |
| static Key KeyOf(Pair kv) { return kv; } | |
| static Value ValueOf(Pair kv) { return kv; } | |
| static inline uword Hash(Key key) { | |
| ASSERT(Thread::Current()->no_safepoint_scope_depth() > 0); | |
| return Utils::WordHash( | |
| CombineHashes(Instructions::Hash(key->instructions()), | |
| static_cast<uword>(key->static_calls_target_table()))); | |
| } | |
| static inline bool IsKeyEqual(Pair pair, Key key) { | |
| // In AOT, disabled code objects should not be considered for deduplication. | |
| ASSERT(!pair->IsDisabled() && !key->IsDisabled()); | |
| if (pair->ptr() == key->ptr()) return true; | |
| // Notice we assume that these entries have already been de-duped, so we | |
| // can use pointer equality. | |
| if (pair->static_calls_target_table() != key->static_calls_target_table()) { | |
| return false; | |
| } | |
| if (pair->pc_descriptors() != key->pc_descriptors()) { | |
| return false; | |
| } | |
| if (pair->compressed_stackmaps() != key->compressed_stackmaps()) { | |
| return false; | |
| } | |
| if (pair->catch_entry_moves_maps() != key->catch_entry_moves_maps()) { | |
| return false; | |
| } | |
| if (pair->exception_handlers() != key->exception_handlers()) { | |
| return false; | |
| } | |
| if (pair->UncheckedEntryPointOffset() != key->UncheckedEntryPointOffset()) { | |
| return false; | |
| } | |
| if (!Instructions::Equals(pair->instructions(), key->instructions())) { | |
| return false; | |
| } | |
| return LoadingUnit::LoadingUnitOf(*pair) == | |
| LoadingUnit::LoadingUnitOf(*key); | |
| } | |
| }; | |
| #endif | |
| void ProgramVisitor::DedupInstructions(Thread* thread) { | |
| class DedupInstructionsVisitor | |
| : public CodeVisitor, | |
| public Deduper<Instructions, InstructionsKeyValueTrait>, | |
| public ObjectVisitor { | |
| public: | |
| explicit DedupInstructionsVisitor(Zone* zone) | |
| : Deduper(zone), | |
| code_(Code::Handle(zone)), | |
| instructions_(Instructions::Handle(zone)) {} | |
| void VisitObject(ObjectPtr obj) override { | |
| if (!obj->IsInstructions()) return; | |
| instructions_ = Instructions::RawCast(obj); | |
| AddCanonical(instructions_); | |
| } | |
| void VisitFunction(const Function& function) override { | |
| if (!function.HasCode()) return; | |
| code_ = function.CurrentCode(); | |
| // This causes the code to be visited once here and once directly in the | |
| // ProgramWalker, but as long as the deduplication process is idempotent, | |
| // the cached entry points won't change during the second visit. | |
| VisitCode(code_); | |
| function.SetInstructionsSafe(code_); // Update cached entry point. | |
| } | |
| void VisitCode(const Code& code) override { | |
| if (code.IsStubCode()) return; | |
| instructions_ = code.instructions(); | |
| instructions_ = Dedup(instructions_); | |
| code.set_instructions(instructions_); | |
| if (code.IsDisabled()) { | |
| instructions_ = code.active_instructions(); | |
| instructions_ = Dedup(instructions_); | |
| } | |
| code.SetActiveInstructionsSafe(instructions_, | |
| code.UncheckedEntryPointOffset()); | |
| } | |
| private: | |
| Code& code_; | |
| Instructions& instructions_; | |
| }; | |
| #if defined(DART_PRECOMPILER) | |
| class DedupInstructionsWithSameMetadataVisitor | |
| : public CodeVisitor, | |
| public Deduper<Code, CodeKeyValueTrait> { | |
| public: | |
| explicit DedupInstructionsWithSameMetadataVisitor(Zone* zone) | |
| : Deduper(zone), | |
| canonical_(Code::Handle(zone)), | |
| code_(Code::Handle(zone)), | |
| instructions_(Instructions::Handle(zone)) {} | |
| // Relink the program graph to eliminate references to the non-canonical | |
| // Code objects. We want to arrive to the graph where Code objects | |
| // and Instruction objects are in one-to-one relationship. | |
| void PostProcess(IsolateGroup* isolate_group) { | |
| const intptr_t canonical_count = canonical_objects_.Length(); | |
| auto& static_calls_array = Array::Handle(zone_); | |
| auto& static_calls_table_entry = Object::Handle(zone_); | |
| auto should_canonicalize = [&](const Object& obj) { | |
| return CanCanonicalize(Code::Cast(obj)); | |
| }; | |
| auto process_pool = [&](const ObjectPool& pool) { | |
| if (pool.IsNull()) { | |
| return; | |
| } | |
| auto& object = Object::Handle(zone_); | |
| for (intptr_t i = 0; i < pool.Length(); i++) { | |
| auto const type = pool.TypeAt(i); | |
| if (type != ObjectPool::EntryType::kTaggedObject) continue; | |
| object = pool.ObjectAt(i); | |
| if (object.IsCode() && should_canonicalize(object)) { | |
| object = Canonicalize(Code::Cast(object)); | |
| pool.SetObjectAt(i, object); | |
| } | |
| } | |
| }; | |
| auto& pool = ObjectPool::Handle(zone_); | |
| auto it = canonical_objects_.GetIterator(); | |
| while (auto canonical_code = it.Next()) { | |
| static_calls_array = (*canonical_code)->static_calls_target_table(); | |
| if (!static_calls_array.IsNull()) { | |
| StaticCallsTable static_calls(static_calls_array); | |
| for (auto& view : static_calls) { | |
| static_calls_table_entry = | |
| view.Get<Code::kSCallTableCodeOrTypeTarget>(); | |
| if (static_calls_table_entry.IsCode() && | |
| should_canonicalize(static_calls_table_entry)) { | |
| static_calls_table_entry = | |
| Canonicalize(Code::Cast(static_calls_table_entry)); | |
| view.Set<Code::kSCallTableCodeOrTypeTarget>( | |
| static_calls_table_entry); | |
| } | |
| } | |
| } | |
| pool = (*canonical_code)->object_pool(); | |
| process_pool(pool); | |
| } | |
| auto object_store = isolate_group->object_store(); | |
| const auto& dispatch_table_entries = | |
| Array::Handle(zone_, object_store->dispatch_table_code_entries()); | |
| if (!dispatch_table_entries.IsNull()) { | |
| auto& code = Code::Handle(zone_); | |
| for (intptr_t i = 0; i < dispatch_table_entries.Length(); i++) { | |
| code ^= dispatch_table_entries.At(i); | |
| if (should_canonicalize(code)) { | |
| code ^= Canonicalize(code); | |
| dispatch_table_entries.SetAt(i, code); | |
| } | |
| } | |
| } | |
| // If there's a global object pool, add any visitable objects. | |
| pool = object_store->global_object_pool(); | |
| process_pool(pool); | |
| RELEASE_ASSERT(canonical_count == canonical_objects_.Length()); | |
| } | |
| void VisitFunction(const Function& function) { | |
| if (!function.HasCode()) return; | |
| code_ = function.CurrentCode(); | |
| // This causes the code to be visited once here and once directly in the | |
| // ProgramWalker, but as long as the deduplication process is idempotent, | |
| // the cached entry points won't change during the second visit. | |
| VisitCode(code_); | |
| function.SetInstructionsSafe(canonical_); // Update cached entry point. | |
| } | |
| void VisitCode(const Code& code) { | |
| if (code.IsStubCode()) return; | |
| canonical_ = code.ptr(); | |
| if (code.IsDisabled()) return; | |
| canonical_ = Canonicalize(code); | |
| instructions_ = canonical_.instructions(); | |
| code.SetActiveInstructionsSafe(instructions_, | |
| code.UncheckedEntryPointOffset()); | |
| code.set_instructions(instructions_); | |
| } | |
| private: | |
| bool CanCanonicalize(const Code& code) const { | |
| return !code.IsDisabled() && !code.IsStubCode(); | |
| } | |
| CodePtr Canonicalize(const Code& code) { | |
| canonical_ = Dedup(code); | |
| if (!code.is_discarded() && canonical_.is_discarded()) { | |
| canonical_.set_is_discarded(false); | |
| } | |
| return canonical_.ptr(); | |
| } | |
| Code& canonical_; | |
| Code& code_; | |
| Instructions& instructions_; | |
| }; | |
| if (FLAG_precompiled_mode) { | |
| StackZone stack_zone(thread); | |
| // CodeKeyValueTrait::Hash is based on object addresses, so make | |
| // sure GC doesn't happen and doesn't move objects. | |
| NoSafepointScope no_safepoint; | |
| DedupInstructionsWithSameMetadataVisitor visitor(thread->zone()); | |
| WalkProgram(thread->zone(), thread->isolate_group(), &visitor); | |
| visitor.PostProcess(thread->isolate_group()); | |
| return; | |
| } | |
| #endif // defined(DART_PRECOMPILER) | |
| StackZone stack_zone(thread); | |
| DedupInstructionsVisitor visitor(thread->zone()); | |
| WalkProgram(thread->zone(), thread->isolate_group(), &visitor); | |
| } | |
| void ProgramVisitor::Dedup(Thread* thread) { | |
| BindStaticCalls(thread); | |
| ShareMegamorphicBuckets(thread); | |
| NormalizeAndDedupCompressedStackMaps(thread); | |
| DedupPcDescriptors(thread); | |
| DedupDeoptEntries(thread); | |
| #if defined(DART_PRECOMPILER) | |
| DedupCatchEntryMovesMaps(thread); | |
| DedupUnlinkedCalls(thread); | |
| PruneSubclasses(thread); | |
| #endif | |
| DedupCodeSourceMaps(thread); | |
| DedupLists(thread); | |
| // Reduces binary size but obfuscates profiler results. | |
| if (FLAG_dedup_instructions) { | |
| DedupInstructions(thread); | |
| } | |
| } | |
| #if defined(DART_PRECOMPILER) | |
| class AssignLoadingUnitsCodeVisitor : public ObjectVisitor { | |
| public: | |
| explicit AssignLoadingUnitsCodeVisitor(Zone* zone) | |
| : heap_(Thread::Current()->heap()), | |
| code_(Code::Handle(zone)), | |
| func_(Function::Handle(zone)), | |
| cls_(Class::Handle(zone)), | |
| lib_(Library::Handle(zone)), | |
| unit_(LoadingUnit::Handle(zone)), | |
| obj_(Object::Handle(zone)) {} | |
| void VisitObject(ObjectPtr obj) override { | |
| if (obj->IsCode()) { | |
| code_ ^= obj; | |
| VisitCode(code_); | |
| } | |
| } | |
| void VisitCode(const Code& code) { | |
| intptr_t id; | |
| if (code.IsFunctionCode()) { | |
| func_ ^= code.function(); | |
| obj_ = func_.Owner(); | |
| cls_ ^= obj_.ptr(); | |
| lib_ = cls_.library(); | |
| if (lib_.IsNull()) { | |
| // E.g., dynamic. | |
| id = LoadingUnit::kRootId; | |
| } else { | |
| unit_ = lib_.loading_unit(); | |
| if (unit_.IsNull()) { | |
| return; // Assignment remains LoadingUnit::kIllegalId | |
| } | |
| id = unit_.id(); | |
| } | |
| } else if (code.IsTypeTestStubCode() || code.IsStubCode() || | |
| code.IsAllocationStubCode()) { | |
| id = LoadingUnit::kRootId; | |
| } else { | |
| UNREACHABLE(); | |
| } | |
| ASSERT(heap_->GetLoadingUnit(code.ptr()) == WeakTable::kNoValue); | |
| heap_->SetLoadingUnit(code.ptr(), id); | |
| obj_ = code.code_source_map(); | |
| MergeAssignment(obj_, id); | |
| obj_ = code.compressed_stackmaps(); | |
| MergeAssignment(obj_, id); | |
| } | |
| void MergeAssignment(const Object& obj, intptr_t id) { | |
| if (obj.IsNull()) return; | |
| intptr_t old_id = heap_->GetLoadingUnit(obj_.ptr()); | |
| if (old_id == WeakTable::kNoValue) { | |
| heap_->SetLoadingUnit(obj_.ptr(), id); | |
| } else if (old_id == id) { | |
| // Shared with another code in the same loading unit. | |
| } else { | |
| // Shared with another code in a different loading unit. | |
| // Could assign to dominating loading unit. | |
| heap_->SetLoadingUnit(obj_.ptr(), LoadingUnit::kRootId); | |
| } | |
| } | |
| private: | |
| Heap* heap_; | |
| Code& code_; | |
| Function& func_; | |
| Class& cls_; | |
| Library& lib_; | |
| LoadingUnit& unit_; | |
| Object& obj_; | |
| }; | |
| void ProgramVisitor::AssignUnits(Thread* thread) { | |
| StackZone stack_zone(thread); | |
| Heap* heap = thread->heap(); | |
| // Oddballs. | |
| heap->SetLoadingUnit(Object::null(), LoadingUnit::kRootId); | |
| heap->SetLoadingUnit(Object::empty_object_pool().ptr(), LoadingUnit::kRootId); | |
| AssignLoadingUnitsCodeVisitor visitor(thread->zone()); | |
| HeapIterationScope iter(thread); | |
| iter.IterateObjects(&visitor); | |
| } | |
| class ProgramHashVisitor : public CodeVisitor { | |
| public: | |
| explicit ProgramHashVisitor(Zone* zone) | |
| : str_(String::Handle(zone)), | |
| pool_(ObjectPool::Handle(zone)), | |
| obj_(Object::Handle(zone)), | |
| instr_(Instructions::Handle(zone)), | |
| hash_(0) {} | |
| void VisitClass(const Class& cls) { | |
| str_ = cls.Name(); | |
| VisitInstance(str_); | |
| } | |
| void VisitFunction(const Function& function) { | |
| str_ = function.name(); | |
| VisitInstance(str_); | |
| } | |
| void VisitCode(const Code& code) { | |
| pool_ = code.object_pool(); | |
| VisitPool(pool_); | |
| instr_ = code.instructions(); | |
| hash_ = CombineHashes(hash_, instr_.Hash()); | |
| } | |
| void VisitPool(const ObjectPool& pool) { | |
| if (pool.IsNull()) return; | |
| for (intptr_t i = 0; i < pool.Length(); i++) { | |
| if (pool.TypeAt(i) == ObjectPool::EntryType::kTaggedObject) { | |
| obj_ = pool.ObjectAt(i); | |
| if (obj_.IsInstance()) { | |
| VisitInstance(Instance::Cast(obj_)); | |
| } | |
| } | |
| } | |
| } | |
| void VisitInstance(const Instance& instance) { | |
| hash_ = CombineHashes(hash_, instance.CanonicalizeHash()); | |
| } | |
| uint32_t hash() const { return FinalizeHash(hash_, String::kHashBits); } | |
| private: | |
| String& str_; | |
| ObjectPool& pool_; | |
| Object& obj_; | |
| Instructions& instr_; | |
| uint32_t hash_; | |
| }; | |
| uint32_t ProgramVisitor::Hash(Thread* thread) { | |
| StackZone stack_zone(thread); | |
| Zone* zone = thread->zone(); | |
| ProgramHashVisitor visitor(zone); | |
| WalkProgram(zone, thread->isolate_group(), &visitor); | |
| visitor.VisitPool(ObjectPool::Handle( | |
| zone, thread->isolate_group()->object_store()->global_object_pool())); | |
| return visitor.hash(); | |
| } | |
| #endif // defined(DART_PRECOMPILER) | |
| } // namespace dart | |
| #endif // defined(DART_PRECOMPILED_RUNTIME) |