// 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. #include "vm/program_visitor.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 { void ProgramVisitor::VisitClasses(ClassVisitor* visitor) { Thread* thread = Thread::Current(); Isolate* isolate = thread->isolate(); Zone* zone = thread->zone(); GrowableObjectArray& libraries = GrowableObjectArray::Handle(zone, isolate->object_store()->libraries()); Library& lib = Library::Handle(zone); Class& cls = Class::Handle(zone); Object& entry = Object::Handle(zone); GrowableObjectArray& patches = GrowableObjectArray::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(); visitor->Visit(cls); } patches = lib.used_scripts(); for (intptr_t j = 0; j < patches.Length(); j++) { entry = patches.At(j); if (entry.IsClass()) { visitor->Visit(Class::Cast(entry)); } } } } class ClassFunctionVisitor : public ClassVisitor { public: ClassFunctionVisitor(Zone* zone, FunctionVisitor* visitor) : visitor_(visitor), functions_(Array::Handle(zone)), function_(Function::Handle(zone)), object_(Object::Handle(zone)), fields_(Array::Handle(zone)), field_(Field::Handle(zone)) {} void Visit(const Class& cls) { functions_ = cls.functions(); for (intptr_t j = 0; j < functions_.Length(); j++) { function_ ^= functions_.At(j); visitor_->Visit(function_); if (function_.HasImplicitClosureFunction()) { function_ = function_.ImplicitClosureFunction(); visitor_->Visit(function_); } } functions_ = cls.invocation_dispatcher_cache(); for (intptr_t j = 0; j < functions_.Length(); j++) { object_ = functions_.At(j); if (object_.IsFunction()) { function_ ^= functions_.At(j); visitor_->Visit(function_); } } fields_ = cls.fields(); for (intptr_t j = 0; j < fields_.Length(); j++) { field_ ^= fields_.At(j); if (field_.is_static() && field_.HasInitializerFunction()) { function_ = field_.InitializerFunction(); visitor_->Visit(function_); } } } private: FunctionVisitor* visitor_; Array& functions_; Function& function_; Object& object_; Array& fields_; Field& field_; }; void ProgramVisitor::VisitFunctions(FunctionVisitor* visitor) { Thread* thread = Thread::Current(); Isolate* isolate = thread->isolate(); Zone* zone = thread->zone(); ClassFunctionVisitor class_visitor(zone, visitor); VisitClasses(&class_visitor); Function& function = Function::Handle(zone); const GrowableObjectArray& closures = GrowableObjectArray::Handle( zone, isolate->object_store()->closure_functions()); ASSERT(!closures.IsNull()); for (intptr_t i = 0; i < closures.Length(); i++) { function ^= closures.At(i); visitor->Visit(function); ASSERT(!function.HasImplicitClosureFunction()); } const auto& global_object_pool = ObjectPool::Handle( zone, isolate->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); if (!object.IsFunction()) continue; visitor->Visit(Function::Cast(object)); } } } class FunctionCodeVisitor : public FunctionVisitor { public: FunctionCodeVisitor(Zone* zone, CodeVisitor* visitor) : visitor_(visitor), code_(Code::Handle(zone)) {} void Visit(const Function& function) { if (!function.HasCode()) return; code_ = function.CurrentCode(); visitor_->Visit(code_); } private: CodeVisitor* const visitor_; Code& code_; }; void ProgramVisitor::VisitCode(CodeVisitor* visitor) { Thread* thread = Thread::Current(); Isolate* isolate = thread->isolate(); Zone* zone = thread->zone(); FunctionCodeVisitor function_visitor(zone, visitor); VisitFunctions(&function_visitor); const auto& dispatch_table_entries = Array::Handle( zone, isolate->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 = Code::RawCast(dispatch_table_entries.At(i)); if (code.IsNull()) continue; visitor->Visit(code); } } } #if !defined(DART_PRECOMPILED_RUNTIME) // 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 class Dedupper : public ValueObject { public: explicit Dedupper(Zone* zone) : zone_(zone), canonical_objects_(zone) {} virtual ~Dedupper() {} void AddVMBaseObjects() { const auto& object_table = Object::vm_isolate_snapshot_object_table(); auto& obj = Object::Handle(zone_); for (intptr_t i = 0; i < object_table.Length(); i++) { obj = object_table.At(i); if (!ShouldAdd(obj)) continue; AddCanonical(T::Cast(obj)); } } 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.raw())); } typename T::RawObjectType* Dedup(const T& obj) { if (ShouldAdd(obj)) { if (auto const canonical = canonical_objects_.LookupValue(&obj)) { return canonical->raw(); } AddCanonical(obj); } return obj.raw(); } Zone* const zone_; DirectChainedHashMap canonical_objects_; }; void ProgramVisitor::BindStaticCalls() { 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 Visit(const Code& code) { table_ = code.static_calls_target_table(); 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(); auto const kind = Code::KindField::decode(kind_and_offset_.Value()); if (kind != Code::kCallViaCode) { ASSERT(!FLAG_precompiled_mode || kind == Code::kPcRelativeCall); only_call_via_code = false; continue; } target_ = view.Get(); if (target_.IsNull()) { target_ = view.Get(); ASSERT(!Code::Cast(target_).IsFunctionCode()); // Allocation stub or AllocateContext or AllocateArray or ... 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 replace 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().raw(); 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. code.set_static_calls_target_table(Object::empty_array()); } } private: Array& table_; Smi& kind_and_offset_; Object& target_; Code& target_code_; }; auto const zone = Thread::Current()->zone(); BindStaticCallsVisitor visitor(zone); ProgramVisitor::VisitCode(&visitor); } DECLARE_FLAG(charp, write_v8_snapshot_profile_to); void ProgramVisitor::ShareMegamorphicBuckets() { Thread* thread = Thread::Current(); Isolate* isolate = thread->isolate(); Zone* zone = thread->zone(); const GrowableObjectArray& table = GrowableObjectArray::Handle( zone, isolate->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, MegamorphicCacheTable::miss_handler(isolate)); 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 ZoneAllocated { public: StackMapEntry(Zone* zone, const CompressedStackMapsIterator& it) : maps_(CompressedStackMaps::Handle(zone, it.maps_.raw())), bits_container_( CompressedStackMaps::Handle(zone, it.bits_container_.raw())), spill_slot_bit_count_(it.current_spill_slot_bit_count_), non_spill_slot_bit_count_(it.current_non_spill_slot_bit_count_), bits_offset_(it.current_bits_offset_) { ASSERT(!maps_.IsNull() && !maps_.IsGlobalTable()); ASSERT(!bits_container_.IsNull()); ASSERT(!maps_.UsesGlobalTable() || bits_container_.IsGlobalTable()); // Check that the iterator was fully loaded when we ran the initializing // expressions above. By this point we enter the body of the constructor, // it's too late to run EnsureFullyLoadedEntry(). ASSERT(it.HasLoadedEntry()); ASSERT(it.current_spill_slot_bit_count_ >= 0); } static const intptr_t kHashBits = 30; intptr_t Hashcode() { if (hash_ != 0) return hash_; uint32_t hash = 0; hash = CombineHashes(hash, spill_slot_bit_count_); hash = CombineHashes(hash, non_spill_slot_bit_count_); for (intptr_t i = 0; i < PayloadLength(); i++) { hash = CombineHashes(hash, PayloadByte(i)); } 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. for (intptr_t i = 0; i < PayloadLength(); i++) { if (PayloadByte(i) != other->PayloadByte(i)) return false; } return true; } // Encodes this StackMapEntry to the given array of bytes and returns the // initial offset of the entry in the array. intptr_t EncodeTo(GrowableArray* array) { auto const current_offset = array->length(); CompressedStackMapsBuilder::EncodeLEB128(array, spill_slot_bit_count_); CompressedStackMapsBuilder::EncodeLEB128(array, non_spill_slot_bit_count_); for (intptr_t i = 0; i < PayloadLength(); i++) { array->Add(PayloadByte(i)); } 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; } intptr_t PayloadByte(intptr_t offset) const { return bits_container_.PayloadByte(bits_offset_ + 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, 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 intptr_t Hashcode(Key key) { return key->Hashcode(); } static bool IsKeyEqual(Pair kv, Key key) { return key->Equals(kv.key); } }; typedef DirectChainedHashMap StackMapEntryIntMap; void ProgramVisitor::NormalizeAndDedupCompressedStackMaps() { // 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 Visit(const Code& code) { compressed_stackmaps_ = code.compressed_stackmaps(); CompressedStackMapsIterator it(compressed_stackmaps_, old_global_table_); while (it.MoveNext()) { it.EnsureFullyLoadedEntry(); 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. RawCompressedStackMaps* 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((*e2)->UsageCount() - (*e1)->UsageCount()); }); GrowableArray bytes; // 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(&bytes)}); } const auto& data = CompressedStackMaps::Handle( zone_, CompressedStackMaps::NewGlobalTable(bytes)); return data.raw(); } private: Zone* const zone_; const CompressedStackMaps& old_global_table_; CompressedStackMaps& compressed_stackmaps_; GrowableArray 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 Dedupper> { public: NormalizeAndDedupCompressedStackMapsVisitor( Zone* zone, const CompressedStackMaps& global_table, const StackMapEntryIntMap& entry_offsets) : Dedupper(zone), old_global_table_(global_table), entry_offsets_(entry_offsets), maps_(CompressedStackMaps::Handle(zone)) { ASSERT(old_global_table_.IsNull() || old_global_table_.IsGlobalTable()); } void Visit(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->raw(); } else { maps_ = NormalizeEntries(maps_); maps_ = Dedup(maps_); } code.set_compressed_stackmaps(maps_); } private: // Creates a normalized CSM from the given non-normalized CSM. RawCompressedStackMaps* NormalizeEntries(const CompressedStackMaps& maps) { GrowableArray new_payload; CompressedStackMapsIterator it(maps, old_global_table_); intptr_t last_offset = 0; while (it.MoveNext()) { it.EnsureFullyLoadedEntry(); StackMapEntry entry(zone_, it); auto const entry_offset = entry_offsets_.LookupValue(&entry); auto const pc_delta = it.pc_offset() - last_offset; CompressedStackMapsBuilder::EncodeLEB128(&new_payload, pc_delta); CompressedStackMapsBuilder::EncodeLEB128(&new_payload, entry_offset); last_offset = it.pc_offset(); } return CompressedStackMaps::NewUsingTable(new_payload); } const CompressedStackMaps& old_global_table_; const StackMapEntryIntMap& entry_offsets_; CompressedStackMaps& maps_; }; // The stack map deduplication happens in two phases: // 1) Visit all CompressedStackMaps (CSM) objects and collect individual entry // info as canonicalized StackMapEntries (SMEs). Also record the number of // times the same entry info was seen across all CSMs in each SME. // // 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). // // 2) Visit all CSMs and replace each with a canonicalized normalized version // that uses the new global table for non-PC offset entry information. Thread* const t = Thread::Current(); StackZone temp_zone(t); HandleScope temp_handles(t); Zone* zone = temp_zone.GetZone(); auto object_store = t->isolate()->object_store(); const auto& old_global_table = CompressedStackMaps::Handle( zone, object_store->canonicalized_stack_map_entries()); CollectStackMapEntriesVisitor collect_visitor(zone, old_global_table); ProgramVisitor::VisitCode(&collect_visitor); // We retrieve the new offsets for CSM entries by creating the new global // table now. We go ahead and put it in place, as we already have a handle // on the old table that we can pass to the normalizing visitor. StackMapEntryIntMap entry_offsets(zone); const auto& new_global_table = CompressedStackMaps::Handle( zone, collect_visitor.CreateGlobalTable(&entry_offsets)); object_store->set_canonicalized_stack_map_entries(new_global_table); NormalizeAndDedupCompressedStackMapsVisitor dedup_visitor( zone, old_global_table, entry_offsets); ProgramVisitor::VisitCode(&dedup_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 intptr_t Hashcode(Key key) { return key->Length(); } static inline bool IsKeyEqual(Pair pair, Key key) { return pair->Equals(*key); } }; void ProgramVisitor::DedupPcDescriptors() { class DedupPcDescriptorsVisitor : public CodeVisitor, public Dedupper, public FunctionVisitor { public: explicit DedupPcDescriptorsVisitor(Zone* zone) : Dedupper(zone), bytecode_(Bytecode::Handle(zone)), pc_descriptor_(PcDescriptors::Handle(zone)) {} void Visit(const Code& code) { pc_descriptor_ = code.pc_descriptors(); pc_descriptor_ = Dedup(pc_descriptor_); code.set_pc_descriptors(pc_descriptor_); } void Visit(const Function& function) { bytecode_ = function.bytecode(); if (bytecode_.IsNull()) return; if (bytecode_.InVMIsolateHeap()) return; pc_descriptor_ = bytecode_.pc_descriptors(); pc_descriptor_ = Dedup(pc_descriptor_); bytecode_.set_pc_descriptors(pc_descriptor_); } private: Bytecode& bytecode_; PcDescriptors& pc_descriptor_; }; auto const zone = Thread::Current()->zone(); DedupPcDescriptorsVisitor visitor(zone); if (Snapshot::IncludesCode(Dart::vm_snapshot_kind())) { // Prefer existing objects in the VM isolate. visitor.AddVMBaseObjects(); } // The function iteration handles the bytecode only, leaving code-related // work for the code iteration. ProgramVisitor::VisitFunctions(&visitor); ProgramVisitor::VisitCode(&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 intptr_t Hashcode(Key key) { return key->CanonicalizeHash(); } static inline bool IsKeyEqual(Pair pair, Key key) { return pair->CanonicalizeEquals(*key); } }; class TypedDataDedupper : public Dedupper { public: explicit TypedDataDedupper(Zone* zone) : Dedupper(zone) {} private: bool IsCorrectType(const Object& obj) const { return obj.IsTypedData(); } }; void ProgramVisitor::DedupDeoptEntries() { class DedupDeoptEntriesVisitor : public CodeVisitor, public TypedDataDedupper { public: explicit DedupDeoptEntriesVisitor(Zone* zone) : TypedDataDedupper(zone), deopt_table_(Array::Handle(zone)), deopt_entry_(TypedData::Handle(zone)), offset_(Smi::Handle(zone)), reason_and_flags_(Smi::Handle(zone)) {} void Visit(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_; }; DedupDeoptEntriesVisitor visitor(Thread::Current()->zone()); ProgramVisitor::VisitCode(&visitor); } #if defined(DART_PRECOMPILER) void ProgramVisitor::DedupCatchEntryMovesMaps() { class DedupCatchEntryMovesMapsVisitor : public CodeVisitor, public TypedDataDedupper { public: explicit DedupCatchEntryMovesMapsVisitor(Zone* zone) : TypedDataDedupper(zone), catch_entry_moves_maps_(TypedData::Handle(zone)) {} void Visit(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; DedupCatchEntryMovesMapsVisitor visitor(Thread::Current()->zone()); ProgramVisitor::VisitCode(&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 intptr_t Hashcode(Key key) { return key->Hashcode(); } static inline bool IsKeyEqual(Pair pair, Key key) { return pair->Equals(*key); } }; void ProgramVisitor::DedupUnlinkedCalls() { class DedupUnlinkedCallsVisitor : public CodeVisitor, public Dedupper { public: explicit DedupUnlinkedCallsVisitor(Zone* zone) : Dedupper(zone), entry_(Object::Handle(zone)), pool_(ObjectPool::Handle(zone)) {} 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 Visit(const Code& code) { pool_ = code.object_pool(); DedupPool(pool_); } private: Object& entry_; ObjectPool& pool_; }; if (!FLAG_precompiled_mode) return; auto const t = Thread::Current(); auto Z = t->zone(); auto const I = t->isolate(); DedupUnlinkedCallsVisitor deduper(Z); auto& gop = ObjectPool::Handle(Z, I->object_store()->global_object_pool()); ASSERT_EQUAL(gop.IsNull(), !FLAG_use_bare_instructions); if (FLAG_use_bare_instructions) { deduper.DedupPool(gop); } // Note: in bare instructions mode we can still have object pools attached // to code objects and these pools need to be deduplicated. // We use these pools to carry information about references between code // objects and other objects in the snapshots (these references are otherwise // implicit and go through global object pool). This information is needed // to produce more informative snapshot profile. if (!FLAG_use_bare_instructions || FLAG_write_v8_snapshot_profile_to != nullptr) { VisitCode(&deduper); } } #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 intptr_t Hashcode(Key key) { ASSERT(!key->IsNull()); return key->Length(); } static inline bool IsKeyEqual(Pair pair, Key key) { ASSERT(!pair->IsNull() && !key->IsNull()); return pair->Equals(*key); } }; void ProgramVisitor::DedupCodeSourceMaps() { class DedupCodeSourceMapsVisitor : public CodeVisitor, public Dedupper { public: explicit DedupCodeSourceMapsVisitor(Zone* zone) : Dedupper(zone), code_source_map_(CodeSourceMap::Handle(zone)) {} void Visit(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_; }; auto const zone = Thread::Current()->zone(); DedupCodeSourceMapsVisitor visitor(zone); if (Snapshot::IncludesCode(Dart::vm_snapshot_kind())) { // Prefer existing objects in the VM isolate. visitor.AddVMBaseObjects(); } ProgramVisitor::VisitCode(&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 intptr_t Hashcode(Key key) { ASSERT(!key->IsNull()); return key->Length(); } 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() { class DedupListsVisitor : public CodeVisitor, public Dedupper, public FunctionVisitor { public: explicit DedupListsVisitor(Zone* zone) : Dedupper(zone), list_(Array::Handle(zone)), function_(Function::Handle(zone)) {} void Visit(const Code& code) { 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 Visit(const Function& function) { list_ = PrepareParameterTypes(function); list_ = Dedup(list_); function.set_parameter_types(list_); list_ = PrepareParameterNames(function); list_ = Dedup(list_); function.set_parameter_names(list_); } private: bool IsCorrectType(const Object& obj) const { return obj.IsArray(); } RawArray* PrepareParameterTypes(const Function& function) { list_ = function.parameter_types(); // Preserve parameter types in the JIT. Needed in case of recompilation // in checked mode, or if available to mirrors, or for copied types to // lazily generated tear offs. Also avoid attempting to change read-only // VM objects for de-duplication. if (FLAG_precompiled_mode && !list_.IsNull() && !list_.InVMIsolateHeap() && !function.IsSignatureFunction() && !function.IsClosureFunction() && !function.IsFfiTrampoline() && function.name() != Symbols::Call().raw()) { // Parameter types not needed for function type tests. for (intptr_t i = 0; i < list_.Length(); i++) { list_.SetAt(i, Object::dynamic_type()); } } return list_.raw(); } RawArray* PrepareParameterNames(const Function& function) { list_ = function.parameter_names(); // Preserve parameter names in case of recompilation for the JIT. Also // avoid attempting to change read-only VM objects for de-duplication. if (FLAG_precompiled_mode && !list_.IsNull() && !list_.InVMIsolateHeap() && !function.HasOptionalNamedParameters()) { // Parameter names not needed for resolution. ASSERT(list_.Length() == function.NumParameters()); for (intptr_t i = 0; i < list_.Length(); i++) { list_.SetAt(i, Symbols::OptimizedOut()); } } return list_.raw(); } Array& list_; Function& function_; }; DedupListsVisitor visitor(Thread::Current()->zone()); ProgramVisitor::VisitFunctions(&visitor); ProgramVisitor::VisitCode(&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 intptr_t Hashcode(Key key) { return key->Size(); } 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 bare instructions mode 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. // // In AOT non-bare instructions mode frames are expanded, like in JIT, and // contain the unique code object. #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 intptr_t Hashcode(Key key) { return key->Size(); } 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->raw() == key->raw()) 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; } return Instructions::Equals(pair->instructions(), key->instructions()); } }; #endif // defined(DART_PRECOMPILER) void ProgramVisitor::DedupInstructions() { class DedupInstructionsVisitor : public CodeVisitor, public Dedupper, public ObjectVisitor { public: explicit DedupInstructionsVisitor(Zone* zone) : Dedupper(zone), function_(Function::Handle(zone)), instructions_(Instructions::Handle(zone)) {} void VisitObject(RawObject* obj) { if (!obj->IsInstructions()) return; instructions_ = Instructions::RawCast(obj); AddCanonical(instructions_); } void Visit(const Code& code) { instructions_ = code.instructions(); instructions_ = Dedup(instructions_); code.SetActiveInstructions(instructions_, code.UncheckedEntryPointOffset()); code.set_instructions(instructions_); if (!code.IsFunctionCode()) return; function_ = code.function(); if (function_.IsNull()) return; function_.SetInstructions(code); // Update cached entry point. } private: Function& function_; Instructions& instructions_; }; DedupInstructionsVisitor visitor(Thread::Current()->zone()); if (Snapshot::IncludesCode(Dart::vm_snapshot_kind())) { // Prefer existing objects in the VM isolate. Dart::vm_isolate()->heap()->VisitObjectsImagePages(&visitor); } ProgramVisitor::VisitCode(&visitor); } void ProgramVisitor::DedupInstructionsWithSameMetadata() { #if defined(DART_PRECOMPILER) class DedupInstructionsWithSameMetadataVisitor : public CodeVisitor, public Dedupper, public ObjectVisitor { public: explicit DedupInstructionsWithSameMetadataVisitor(Zone* zone) : Dedupper(zone), canonical_(Code::Handle(zone)), function_(Function::Handle(zone)), instructions_(Instructions::Handle(zone)) {} void VisitObject(RawObject* obj) { if (!obj->IsCode()) return; canonical_ = Code::RawCast(obj); AddCanonical(canonical_); } void Visit(const Code& code) { if (code.IsDisabled()) return; canonical_ = Dedup(code); instructions_ = canonical_.instructions(); code.SetActiveInstructions(instructions_, code.UncheckedEntryPointOffset()); code.set_instructions(instructions_); if (!code.IsFunctionCode()) return; function_ = code.function(); if (function_.IsNull()) return; function_.SetInstructions(code); // Update cached entry point. } private: bool CanCanonicalize(const Code& code) const { return !code.IsDisabled(); } Code& canonical_; Function& function_; Instructions& instructions_; }; DedupInstructionsWithSameMetadataVisitor visitor(Thread::Current()->zone()); ProgramVisitor::VisitCode(&visitor); #endif // defined(DART_PRECOMPILER) } #endif // !defined(DART_PRECOMPILED_RUNTIME) void ProgramVisitor::Dedup() { #if !defined(DART_PRECOMPILED_RUNTIME) Thread* thread = Thread::Current(); StackZone stack_zone(thread); HANDLESCOPE(thread); BindStaticCalls(); ShareMegamorphicBuckets(); NormalizeAndDedupCompressedStackMaps(); DedupPcDescriptors(); NOT_IN_PRECOMPILED(DedupDeoptEntries()); #if defined(DART_PRECOMPILER) DedupCatchEntryMovesMaps(); DedupUnlinkedCalls(); #endif DedupCodeSourceMaps(); DedupLists(); // Reduces binary size but obfuscates profiler results. if (FLAG_dedup_instructions) { if (FLAG_precompiled_mode && FLAG_use_bare_instructions) { DedupInstructionsWithSameMetadata(); } else { DedupInstructions(); } } #endif // !defined(DART_PRECOMPILED_RUNTIME) } } // namespace dart