d9226ff693
Change-Id: I94767c7e1d262ce7a2f2aaced4cf9fa9abe09425 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/136705 Reviewed-by: Alexander Markov <alexmarkov@google.com> Commit-Queue: Ryan Macnak <rmacnak@google.com>
1134 lines
39 KiB
C++
1134 lines
39 KiB
C++
// 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());
|
|
for (intptr_t i = 0; i < closures.Length(); i++) {
|
|
function ^= closures.At(i);
|
|
visitor->Visit(function);
|
|
ASSERT(!function.HasImplicitClosureFunction());
|
|
}
|
|
}
|
|
|
|
#if !defined(DART_PRECOMPILED_RUNTIME)
|
|
void ProgramVisitor::BindStaticCalls() {
|
|
if (FLAG_precompiled_mode) {
|
|
return;
|
|
}
|
|
|
|
class BindJITStaticCallsVisitor : public FunctionVisitor {
|
|
public:
|
|
explicit BindJITStaticCallsVisitor(Zone* zone)
|
|
: code_(Code::Handle(zone)),
|
|
table_(Array::Handle(zone)),
|
|
kind_and_offset_(Smi::Handle(zone)),
|
|
target_(Object::Handle(zone)),
|
|
target_code_(Code::Handle(zone)) {}
|
|
|
|
void Visit(const Function& function) {
|
|
if (!function.HasCode()) {
|
|
return;
|
|
}
|
|
code_ = function.CurrentCode();
|
|
table_ = code_.static_calls_target_table();
|
|
StaticCallsTable static_calls(table_);
|
|
for (const auto& view : static_calls) {
|
|
kind_and_offset_ = view.Get<Code::kSCallTableKindAndOffset>();
|
|
Code::CallKind kind = Code::KindField::decode(kind_and_offset_.Value());
|
|
if (kind != Code::kCallViaCode) {
|
|
continue;
|
|
}
|
|
int32_t pc_offset = Code::OffsetField::decode(kind_and_offset_.Value());
|
|
target_ = view.Get<Code::kSCallTableFunctionTarget>();
|
|
if (target_.IsNull()) {
|
|
target_ = view.Get<Code::kSCallTableCodeTarget>();
|
|
ASSERT(!Code::Cast(target_).IsFunctionCode());
|
|
// Allocation stub or AllocateContext or AllocateArray or ...
|
|
} else {
|
|
const Function& target_func = Function::Cast(target_);
|
|
if (target_func.HasCode()) {
|
|
target_code_ = target_func.CurrentCode();
|
|
} else {
|
|
target_code_ = StubCode::CallStaticFunction().raw();
|
|
}
|
|
uword pc = pc_offset + code_.PayloadStart();
|
|
CodePatcher::PatchStaticCallAt(pc, code_, target_code_);
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
Code& code_;
|
|
Array& table_;
|
|
Smi& kind_and_offset_;
|
|
Object& target_;
|
|
Code& target_code_;
|
|
};
|
|
|
|
BindJITStaticCallsVisitor visitor(Thread::Current()->zone());
|
|
ProgramVisitor::VisitFunctions(&visitor);
|
|
}
|
|
|
|
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<uint8_t>* 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<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 intptr_t Hashcode(Key key) { return key->Hashcode(); }
|
|
static bool IsKeyEqual(Pair kv, Key key) { return key->Equals(kv.key); }
|
|
};
|
|
|
|
typedef DirectChainedHashMap<StackMapEntryKeyIntValueTrait> StackMapEntryIntMap;
|
|
|
|
typedef DirectChainedHashMap<PointerKeyValueTrait<const CompressedStackMaps>>
|
|
CompressedStackMapsSet;
|
|
|
|
void ProgramVisitor::NormalizeAndDedupCompressedStackMaps() {
|
|
// Walks all the CSMs in Code objects and collects their entry information
|
|
// for consolidation.
|
|
class CollectStackMapEntriesVisitor : public FunctionVisitor {
|
|
public:
|
|
CollectStackMapEntriesVisitor(Zone* zone,
|
|
const CompressedStackMaps& global_table)
|
|
: zone_(zone),
|
|
old_global_table_(global_table),
|
|
code_(Code::Handle(zone)),
|
|
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 Function& function) {
|
|
if (!function.HasCode()) return;
|
|
code_ = function.CurrentCode();
|
|
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<int>((*e2)->UsageCount() - (*e1)->UsageCount());
|
|
});
|
|
GrowableArray<uint8_t> 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_;
|
|
|
|
Code& code_;
|
|
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 FunctionVisitor {
|
|
public:
|
|
NormalizeAndDedupCompressedStackMapsVisitor(
|
|
Zone* zone,
|
|
const CompressedStackMaps& global_table,
|
|
const StackMapEntryIntMap& entry_offsets)
|
|
: zone_(zone),
|
|
old_global_table_(global_table),
|
|
entry_offsets_(entry_offsets),
|
|
canonical_compressed_stackmaps_set_(),
|
|
code_(Code::Handle(zone)),
|
|
compressed_stackmaps_(CompressedStackMaps::Handle(zone)),
|
|
current_normalized_maps_(CompressedStackMaps::Handle(zone)) {
|
|
ASSERT(old_global_table_.IsNull() || old_global_table_.IsGlobalTable());
|
|
}
|
|
|
|
// Creates a normalized CSM from the given non-normalized CSM.
|
|
RawCompressedStackMaps* NormalizeEntries(const CompressedStackMaps& maps) {
|
|
GrowableArray<uint8_t> 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);
|
|
}
|
|
|
|
RawCompressedStackMaps* NormalizeAndDedupCompressedStackMaps(
|
|
const CompressedStackMaps& maps) {
|
|
ASSERT(!maps.IsNull());
|
|
// First check is to make sure [maps] hasn't already been normalized,
|
|
// since any normalized map already has a canonical entry in the set.
|
|
auto canonical_maps =
|
|
canonical_compressed_stackmaps_set_.LookupValue(&maps);
|
|
if (canonical_maps != nullptr) return canonical_maps->raw();
|
|
current_normalized_maps_ = NormalizeEntries(compressed_stackmaps_);
|
|
// Use the canonical entry for the newly normalized CSM, if one exists.
|
|
canonical_maps = canonical_compressed_stackmaps_set_.LookupValue(
|
|
¤t_normalized_maps_);
|
|
if (canonical_maps != nullptr) return canonical_maps->raw();
|
|
canonical_compressed_stackmaps_set_.Insert(
|
|
&CompressedStackMaps::ZoneHandle(zone_,
|
|
current_normalized_maps_.raw()));
|
|
return current_normalized_maps_.raw();
|
|
}
|
|
|
|
void Visit(const Function& function) {
|
|
if (!function.HasCode()) return;
|
|
code_ = function.CurrentCode();
|
|
compressed_stackmaps_ = code_.compressed_stackmaps();
|
|
// We represent empty CSMs as the null value, and thus those don't need to
|
|
// be normalized or deduped.
|
|
if (compressed_stackmaps_.IsNull()) return;
|
|
compressed_stackmaps_ =
|
|
NormalizeAndDedupCompressedStackMaps(compressed_stackmaps_);
|
|
code_.set_compressed_stackmaps(compressed_stackmaps_);
|
|
}
|
|
|
|
private:
|
|
Zone* const zone_;
|
|
const CompressedStackMaps& old_global_table_;
|
|
const StackMapEntryIntMap& entry_offsets_;
|
|
CompressedStackMapsSet canonical_compressed_stackmaps_set_;
|
|
Code& code_;
|
|
CompressedStackMaps& compressed_stackmaps_;
|
|
CompressedStackMaps& current_normalized_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::VisitFunctions(&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::VisitFunctions(&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);
|
|
}
|
|
};
|
|
|
|
typedef DirectChainedHashMap<PcDescriptorsKeyValueTrait> PcDescriptorsSet;
|
|
|
|
void ProgramVisitor::DedupPcDescriptors() {
|
|
class DedupPcDescriptorsVisitor : public FunctionVisitor {
|
|
public:
|
|
explicit DedupPcDescriptorsVisitor(Zone* zone)
|
|
: zone_(zone),
|
|
canonical_pc_descriptors_(),
|
|
bytecode_(Bytecode::Handle(zone)),
|
|
code_(Code::Handle(zone)),
|
|
pc_descriptor_(PcDescriptors::Handle(zone)) {}
|
|
|
|
void AddPcDescriptor(const PcDescriptors& pc_descriptor) {
|
|
canonical_pc_descriptors_.Insert(
|
|
&PcDescriptors::ZoneHandle(zone_, pc_descriptor.raw()));
|
|
}
|
|
|
|
void Visit(const Function& function) {
|
|
bytecode_ = function.bytecode();
|
|
if (!bytecode_.IsNull() && !bytecode_.InVMIsolateHeap()) {
|
|
pc_descriptor_ = bytecode_.pc_descriptors();
|
|
if (!pc_descriptor_.IsNull()) {
|
|
pc_descriptor_ = DedupPcDescriptor(pc_descriptor_);
|
|
bytecode_.set_pc_descriptors(pc_descriptor_);
|
|
}
|
|
}
|
|
if (!function.HasCode()) {
|
|
return;
|
|
}
|
|
code_ = function.CurrentCode();
|
|
pc_descriptor_ = code_.pc_descriptors();
|
|
if (pc_descriptor_.IsNull()) return;
|
|
pc_descriptor_ = DedupPcDescriptor(pc_descriptor_);
|
|
code_.set_pc_descriptors(pc_descriptor_);
|
|
}
|
|
|
|
RawPcDescriptors* DedupPcDescriptor(const PcDescriptors& pc_descriptor) {
|
|
const PcDescriptors* canonical_pc_descriptor =
|
|
canonical_pc_descriptors_.LookupValue(&pc_descriptor);
|
|
if (canonical_pc_descriptor == NULL) {
|
|
AddPcDescriptor(pc_descriptor);
|
|
return pc_descriptor.raw();
|
|
} else {
|
|
return canonical_pc_descriptor->raw();
|
|
}
|
|
}
|
|
|
|
private:
|
|
Zone* zone_;
|
|
PcDescriptorsSet canonical_pc_descriptors_;
|
|
Bytecode& bytecode_;
|
|
Code& code_;
|
|
PcDescriptors& pc_descriptor_;
|
|
};
|
|
|
|
DedupPcDescriptorsVisitor visitor(Thread::Current()->zone());
|
|
if (Snapshot::IncludesCode(Dart::vm_snapshot_kind())) {
|
|
// Prefer existing objects in the VM isolate.
|
|
const Array& object_table = Object::vm_isolate_snapshot_object_table();
|
|
Object& object = Object::Handle();
|
|
for (intptr_t i = 0; i < object_table.Length(); i++) {
|
|
object = object_table.At(i);
|
|
if (object.IsPcDescriptors()) {
|
|
visitor.AddPcDescriptor(PcDescriptors::Cast(object));
|
|
}
|
|
}
|
|
}
|
|
ProgramVisitor::VisitFunctions(&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);
|
|
}
|
|
};
|
|
|
|
typedef DirectChainedHashMap<TypedDataKeyValueTrait> TypedDataSet;
|
|
|
|
void ProgramVisitor::DedupDeoptEntries() {
|
|
class DedupDeoptEntriesVisitor : public FunctionVisitor {
|
|
public:
|
|
explicit DedupDeoptEntriesVisitor(Zone* zone)
|
|
: zone_(zone),
|
|
canonical_deopt_entries_(),
|
|
code_(Code::Handle(zone)),
|
|
deopt_table_(Array::Handle(zone)),
|
|
deopt_entry_(TypedData::Handle(zone)),
|
|
offset_(Smi::Handle(zone)),
|
|
reason_and_flags_(Smi::Handle(zone)) {}
|
|
|
|
void Visit(const Function& function) {
|
|
if (!function.HasCode()) {
|
|
return;
|
|
}
|
|
code_ = function.CurrentCode();
|
|
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_ = DedupDeoptEntry(deopt_entry_);
|
|
ASSERT(!deopt_entry_.IsNull());
|
|
DeoptTable::SetEntry(deopt_table_, i, offset_, deopt_entry_,
|
|
reason_and_flags_);
|
|
}
|
|
}
|
|
|
|
RawTypedData* DedupDeoptEntry(const TypedData& deopt_entry) {
|
|
const TypedData* canonical_deopt_entry =
|
|
canonical_deopt_entries_.LookupValue(&deopt_entry);
|
|
if (canonical_deopt_entry == NULL) {
|
|
canonical_deopt_entries_.Insert(
|
|
&TypedData::ZoneHandle(zone_, deopt_entry.raw()));
|
|
return deopt_entry.raw();
|
|
} else {
|
|
return canonical_deopt_entry->raw();
|
|
}
|
|
}
|
|
|
|
private:
|
|
Zone* zone_;
|
|
TypedDataSet canonical_deopt_entries_;
|
|
Code& code_;
|
|
Array& deopt_table_;
|
|
TypedData& deopt_entry_;
|
|
Smi& offset_;
|
|
Smi& reason_and_flags_;
|
|
};
|
|
|
|
DedupDeoptEntriesVisitor visitor(Thread::Current()->zone());
|
|
ProgramVisitor::VisitFunctions(&visitor);
|
|
}
|
|
|
|
#if defined(DART_PRECOMPILER)
|
|
void ProgramVisitor::DedupCatchEntryMovesMaps() {
|
|
if (!FLAG_precompiled_mode) {
|
|
return;
|
|
}
|
|
class DedupCatchEntryMovesMapsVisitor : public FunctionVisitor {
|
|
public:
|
|
explicit DedupCatchEntryMovesMapsVisitor(Zone* zone)
|
|
: zone_(zone),
|
|
canonical_catch_entry_moves_maps_(),
|
|
code_(Code::Handle(zone)),
|
|
catch_entry_moves_maps_(TypedData::Handle(zone)) {}
|
|
|
|
void Visit(const Function& function) {
|
|
if (!function.HasCode()) {
|
|
return;
|
|
}
|
|
code_ = function.CurrentCode();
|
|
catch_entry_moves_maps_ = code_.catch_entry_moves_maps();
|
|
catch_entry_moves_maps_ =
|
|
DedupCatchEntryMovesMaps(catch_entry_moves_maps_);
|
|
code_.set_catch_entry_moves_maps(catch_entry_moves_maps_);
|
|
}
|
|
|
|
RawTypedData* DedupCatchEntryMovesMaps(
|
|
const TypedData& catch_entry_moves_maps) {
|
|
const TypedData* canonical_catch_entry_moves_maps =
|
|
canonical_catch_entry_moves_maps_.LookupValue(
|
|
&catch_entry_moves_maps);
|
|
if (canonical_catch_entry_moves_maps == NULL) {
|
|
canonical_catch_entry_moves_maps_.Insert(
|
|
&TypedData::ZoneHandle(zone_, catch_entry_moves_maps.raw()));
|
|
return catch_entry_moves_maps.raw();
|
|
} else {
|
|
return canonical_catch_entry_moves_maps->raw();
|
|
}
|
|
}
|
|
|
|
private:
|
|
Zone* zone_;
|
|
TypedDataSet canonical_catch_entry_moves_maps_;
|
|
Code& code_;
|
|
TypedData& catch_entry_moves_maps_;
|
|
};
|
|
|
|
DedupCatchEntryMovesMapsVisitor visitor(Thread::Current()->zone());
|
|
ProgramVisitor::VisitFunctions(&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 intptr_t Hashcode(Key key) { return key->Length(); }
|
|
|
|
static inline bool IsKeyEqual(Pair pair, Key key) {
|
|
return pair->Equals(*key);
|
|
}
|
|
};
|
|
|
|
typedef DirectChainedHashMap<CodeSourceMapKeyValueTrait> CodeSourceMapSet;
|
|
|
|
void ProgramVisitor::DedupCodeSourceMaps() {
|
|
class DedupCodeSourceMapsVisitor : public FunctionVisitor {
|
|
public:
|
|
explicit DedupCodeSourceMapsVisitor(Zone* zone)
|
|
: zone_(zone),
|
|
canonical_code_source_maps_(),
|
|
code_(Code::Handle(zone)),
|
|
code_source_map_(CodeSourceMap::Handle(zone)) {}
|
|
|
|
void AddCodeSourceMap(const CodeSourceMap& code_source_map) {
|
|
canonical_code_source_maps_.Insert(
|
|
&CodeSourceMap::ZoneHandle(zone_, code_source_map.raw()));
|
|
}
|
|
|
|
void Visit(const Function& function) {
|
|
if (!function.HasCode()) {
|
|
return;
|
|
}
|
|
code_ = function.CurrentCode();
|
|
code_source_map_ = code_.code_source_map();
|
|
ASSERT(!code_source_map_.IsNull());
|
|
code_source_map_ = DedupCodeSourceMap(code_source_map_);
|
|
code_.set_code_source_map(code_source_map_);
|
|
}
|
|
|
|
RawCodeSourceMap* DedupCodeSourceMap(const CodeSourceMap& code_source_map) {
|
|
const CodeSourceMap* canonical_code_source_map =
|
|
canonical_code_source_maps_.LookupValue(&code_source_map);
|
|
if (canonical_code_source_map == NULL) {
|
|
AddCodeSourceMap(code_source_map);
|
|
return code_source_map.raw();
|
|
} else {
|
|
return canonical_code_source_map->raw();
|
|
}
|
|
}
|
|
|
|
private:
|
|
Zone* zone_;
|
|
CodeSourceMapSet canonical_code_source_maps_;
|
|
Code& code_;
|
|
CodeSourceMap& code_source_map_;
|
|
};
|
|
|
|
DedupCodeSourceMapsVisitor visitor(Thread::Current()->zone());
|
|
if (Snapshot::IncludesCode(Dart::vm_snapshot_kind())) {
|
|
// Prefer existing objects in the VM isolate.
|
|
const Array& object_table = Object::vm_isolate_snapshot_object_table();
|
|
Object& object = Object::Handle();
|
|
for (intptr_t i = 0; i < object_table.Length(); i++) {
|
|
object = object_table.At(i);
|
|
if (object.IsCodeSourceMap()) {
|
|
visitor.AddCodeSourceMap(CodeSourceMap::Cast(object));
|
|
}
|
|
}
|
|
}
|
|
ProgramVisitor::VisitFunctions(&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) { return key->Length(); }
|
|
|
|
static inline bool IsKeyEqual(Pair pair, Key key) {
|
|
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;
|
|
}
|
|
};
|
|
|
|
typedef DirectChainedHashMap<ArrayKeyValueTrait> ArraySet;
|
|
|
|
void ProgramVisitor::DedupLists() {
|
|
class DedupListsVisitor : public FunctionVisitor {
|
|
public:
|
|
explicit DedupListsVisitor(Zone* zone)
|
|
: zone_(zone),
|
|
canonical_lists_(),
|
|
code_(Code::Handle(zone)),
|
|
list_(Array::Handle(zone)) {}
|
|
|
|
void Visit(const Function& function) {
|
|
code_ = function.CurrentCode();
|
|
if (!code_.IsNull()) {
|
|
list_ = code_.inlined_id_to_function();
|
|
if (!list_.IsNull()) {
|
|
list_ = DedupList(list_);
|
|
code_.set_inlined_id_to_function(list_);
|
|
}
|
|
list_ = code_.deopt_info_array();
|
|
if (!list_.IsNull()) {
|
|
list_ = DedupList(list_);
|
|
code_.set_deopt_info_array(list_);
|
|
}
|
|
list_ = code_.static_calls_target_table();
|
|
if (!list_.IsNull()) {
|
|
list_ = DedupList(list_);
|
|
code_.set_static_calls_target_table(list_);
|
|
}
|
|
}
|
|
|
|
list_ = function.parameter_types();
|
|
if (!list_.IsNull()) {
|
|
// 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.
|
|
if (FLAG_precompiled_mode) {
|
|
if (!function.IsSignatureFunction() &&
|
|
!function.IsClosureFunction() &&
|
|
(function.name() != Symbols::Call().raw()) &&
|
|
!list_.InVMIsolateHeap()) {
|
|
// Parameter types not needed for function type tests.
|
|
for (intptr_t i = 0; i < list_.Length(); i++) {
|
|
list_.SetAt(i, Object::dynamic_type());
|
|
}
|
|
}
|
|
}
|
|
list_ = DedupList(list_);
|
|
function.set_parameter_types(list_);
|
|
}
|
|
|
|
list_ = function.parameter_names();
|
|
if (!list_.IsNull()) {
|
|
// Preserve parameter names in case of recompilation for the JIT.
|
|
if (FLAG_precompiled_mode) {
|
|
if (!function.HasOptionalNamedParameters() &&
|
|
!list_.InVMIsolateHeap()) {
|
|
// Parameter names not needed for resolution.
|
|
for (intptr_t i = 0; i < list_.Length(); i++) {
|
|
list_.SetAt(i, Symbols::OptimizedOut());
|
|
}
|
|
}
|
|
}
|
|
list_ = DedupList(list_);
|
|
function.set_parameter_names(list_);
|
|
}
|
|
}
|
|
|
|
RawArray* DedupList(const Array& list) {
|
|
if (list.InVMIsolateHeap()) {
|
|
// Avoid using read-only VM objects for de-duplication.
|
|
return list.raw();
|
|
}
|
|
const Array* canonical_list = canonical_lists_.LookupValue(&list);
|
|
if (canonical_list == NULL) {
|
|
canonical_lists_.Insert(&Array::ZoneHandle(zone_, list.raw()));
|
|
return list.raw();
|
|
} else {
|
|
return canonical_list->raw();
|
|
}
|
|
}
|
|
|
|
private:
|
|
Zone* zone_;
|
|
ArraySet canonical_lists_;
|
|
Code& code_;
|
|
Array& list_;
|
|
};
|
|
|
|
DedupListsVisitor visitor(Thread::Current()->zone());
|
|
ProgramVisitor::VisitFunctions(&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);
|
|
}
|
|
};
|
|
|
|
typedef DirectChainedHashMap<InstructionsKeyValueTrait> InstructionsSet;
|
|
|
|
// 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());
|
|
}
|
|
};
|
|
|
|
typedef DirectChainedHashMap<CodeKeyValueTrait> CodeSet;
|
|
#endif // defined(DART_PRECOMPILER)
|
|
|
|
void ProgramVisitor::DedupInstructions() {
|
|
class DedupInstructionsVisitor : public FunctionVisitor,
|
|
public ObjectVisitor {
|
|
public:
|
|
explicit DedupInstructionsVisitor(Zone* zone)
|
|
: zone_(zone),
|
|
canonical_instructions_set_(),
|
|
code_(Code::Handle(zone)),
|
|
instructions_(Instructions::Handle(zone)) {}
|
|
|
|
void VisitObject(RawObject* obj) {
|
|
if (obj->IsInstructions()) {
|
|
canonical_instructions_set_.Insert(
|
|
&Instructions::ZoneHandle(zone_, Instructions::RawCast(obj)));
|
|
}
|
|
}
|
|
|
|
void Visit(const Function& function) {
|
|
if (!function.HasCode()) {
|
|
return;
|
|
}
|
|
code_ = function.CurrentCode();
|
|
instructions_ = code_.instructions();
|
|
instructions_ = DedupOneInstructions(instructions_);
|
|
code_.SetActiveInstructions(instructions_,
|
|
code_.UncheckedEntryPointOffset());
|
|
code_.set_instructions(instructions_);
|
|
function.SetInstructions(code_); // Update cached entry point.
|
|
}
|
|
|
|
RawInstructions* DedupOneInstructions(const Instructions& instructions) {
|
|
const Instructions* canonical_instructions =
|
|
canonical_instructions_set_.LookupValue(&instructions);
|
|
if (canonical_instructions == NULL) {
|
|
canonical_instructions_set_.Insert(
|
|
&Instructions::ZoneHandle(zone_, instructions.raw()));
|
|
return instructions.raw();
|
|
} else {
|
|
return canonical_instructions->raw();
|
|
}
|
|
}
|
|
|
|
private:
|
|
Zone* zone_;
|
|
InstructionsSet canonical_instructions_set_;
|
|
Code& code_;
|
|
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::VisitFunctions(&visitor);
|
|
}
|
|
|
|
void ProgramVisitor::DedupInstructionsWithSameMetadata() {
|
|
#if defined(DART_PRECOMPILER)
|
|
class DedupInstructionsWithSameMetadataVisitor : public FunctionVisitor,
|
|
public ObjectVisitor {
|
|
public:
|
|
explicit DedupInstructionsWithSameMetadataVisitor(Zone* zone)
|
|
: zone_(zone),
|
|
canonical_set_(),
|
|
code_(Code::Handle(zone)),
|
|
instructions_(Instructions::Handle(zone)) {}
|
|
|
|
void VisitObject(RawObject* obj) {
|
|
if (obj->IsCode()) {
|
|
const auto code = Code::RawCast(obj);
|
|
if (Code::IsDisabled(code)) return;
|
|
canonical_set_.Insert(&Code::ZoneHandle(zone_, code));
|
|
}
|
|
}
|
|
|
|
void Visit(const Function& function) {
|
|
if (!function.HasCode() || Code::IsDisabled(function.CurrentCode())) {
|
|
return;
|
|
}
|
|
code_ = function.CurrentCode();
|
|
instructions_ = DedupOneInstructions(function, code_);
|
|
code_.SetActiveInstructions(instructions_,
|
|
code_.UncheckedEntryPointOffset());
|
|
code_.set_instructions(instructions_);
|
|
function.SetInstructions(code_); // Update cached entry point.
|
|
}
|
|
|
|
RawInstructions* DedupOneInstructions(const Function& function,
|
|
const Code& code) {
|
|
const Code* canonical = canonical_set_.LookupValue(&code);
|
|
if (canonical == nullptr) {
|
|
canonical_set_.Insert(&Code::ZoneHandle(zone_, code.raw()));
|
|
return code.instructions();
|
|
} else {
|
|
return canonical->instructions();
|
|
}
|
|
}
|
|
|
|
private:
|
|
Zone* zone_;
|
|
CodeSet canonical_set_;
|
|
Code& code_;
|
|
Instructions& instructions_;
|
|
};
|
|
|
|
DedupInstructionsWithSameMetadataVisitor visitor(Thread::Current()->zone());
|
|
ProgramVisitor::VisitFunctions(&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();
|
|
#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
|