| Index: runtime/vm/flow_graph_optimizer.cc
|
| diff --git a/runtime/vm/flow_graph_optimizer.cc b/runtime/vm/flow_graph_optimizer.cc
|
| index 3fef94d1dd660ad14517f289f8bd64f1ace672f8..b37b654e9206d25da89e68caad4b76648ce0cc95 100644
|
| --- a/runtime/vm/flow_graph_optimizer.cc
|
| +++ b/runtime/vm/flow_graph_optimizer.cc
|
| @@ -32,16 +32,12 @@ DEFINE_FLAG(int, getter_setter_ratio, 13,
|
| DEFINE_FLAG(bool, guess_icdata_cid, true,
|
| "Artificially create type feedback for arithmetic etc. operations"
|
| " by guessing the other unknown argument cid");
|
| -DEFINE_FLAG(bool, load_cse, true, "Use redundant load elimination.");
|
| -DEFINE_FLAG(bool, dead_store_elimination, true, "Eliminate dead stores");
|
| DEFINE_FLAG(int, max_polymorphic_checks, 4,
|
| "Maximum number of polymorphic check, otherwise it is megamorphic.");
|
| DEFINE_FLAG(int, max_equality_polymorphic_checks, 32,
|
| "Maximum number of polymorphic checks in equality operator,"
|
| " otherwise use megamorphic dispatch.");
|
| DEFINE_FLAG(bool, merge_sin_cos, false, "Merge sin/cos into sincos");
|
| -DEFINE_FLAG(bool, trace_load_optimization, false,
|
| - "Print live sets for load optimization pass.");
|
| DEFINE_FLAG(bool, trace_optimization, false, "Print optimization details.");
|
| DEFINE_FLAG(bool, truncating_left_shift, true,
|
| "Optimize left shift to truncate if possible");
|
| @@ -58,7 +54,6 @@ DECLARE_FLAG(bool, trace_cha);
|
| DECLARE_FLAG(bool, trace_field_guards);
|
| DECLARE_FLAG(bool, trace_type_check_elimination);
|
| DECLARE_FLAG(bool, warn_on_javascript_compatibility);
|
| -DECLARE_FLAG(bool, fields_may_be_reset);
|
|
|
| // Quick access to the current isolate and zone.
|
| #define I (isolate())
|
| @@ -642,44 +637,6 @@ void FlowGraphOptimizer::TryOptimizePatterns() {
|
| }
|
|
|
|
|
| -static void EnsureSSATempIndex(FlowGraph* graph,
|
| - Definition* defn,
|
| - Definition* replacement) {
|
| - if ((replacement->ssa_temp_index() == -1) &&
|
| - (defn->ssa_temp_index() != -1)) {
|
| - graph->AllocateSSAIndexes(replacement);
|
| - }
|
| -}
|
| -
|
| -
|
| -static void ReplaceCurrentInstruction(ForwardInstructionIterator* iterator,
|
| - Instruction* current,
|
| - Instruction* replacement,
|
| - FlowGraph* graph) {
|
| - Definition* current_defn = current->AsDefinition();
|
| - if ((replacement != NULL) && (current_defn != NULL)) {
|
| - Definition* replacement_defn = replacement->AsDefinition();
|
| - ASSERT(replacement_defn != NULL);
|
| - current_defn->ReplaceUsesWith(replacement_defn);
|
| - EnsureSSATempIndex(graph, current_defn, replacement_defn);
|
| -
|
| - if (FLAG_trace_optimization) {
|
| - THR_Print("Replacing v%" Pd " with v%" Pd "\n",
|
| - current_defn->ssa_temp_index(),
|
| - replacement_defn->ssa_temp_index());
|
| - }
|
| - } else if (FLAG_trace_optimization) {
|
| - if (current_defn == NULL) {
|
| - THR_Print("Removing %s\n", current->DebugName());
|
| - } else {
|
| - ASSERT(!current_defn->HasUses());
|
| - THR_Print("Removing v%" Pd ".\n", current_defn->ssa_temp_index());
|
| - }
|
| - }
|
| - iterator->RemoveCurrentFromGraph();
|
| -}
|
| -
|
| -
|
| bool FlowGraphOptimizer::Canonicalize() {
|
| bool changed = false;
|
| for (intptr_t i = 0; i < block_order_.length(); ++i) {
|
| @@ -698,7 +655,7 @@ bool FlowGraphOptimizer::Canonicalize() {
|
| // For non-definitions Canonicalize should return either NULL or
|
| // this.
|
| ASSERT((replacement == NULL) || current->IsDefinition());
|
| - ReplaceCurrentInstruction(&it, current, replacement, flow_graph_);
|
| + flow_graph_->ReplaceCurrentInstruction(&it, current, replacement);
|
| changed = true;
|
| }
|
| }
|
| @@ -5073,3465 +5030,24 @@ void FlowGraphOptimizer::InferIntRanges() {
|
| }
|
|
|
|
|
| -void TryCatchAnalyzer::Optimize(FlowGraph* flow_graph) {
|
| - // For every catch-block: Iterate over all call instructions inside the
|
| - // corresponding try-block and figure out for each environment value if it
|
| - // is the same constant at all calls. If yes, replace the initial definition
|
| - // at the catch-entry with this constant.
|
| - const GrowableArray<CatchBlockEntryInstr*>& catch_entries =
|
| - flow_graph->graph_entry()->catch_entries();
|
| - intptr_t base = kFirstLocalSlotFromFp + flow_graph->num_non_copied_params();
|
| - for (intptr_t catch_idx = 0;
|
| - catch_idx < catch_entries.length();
|
| - ++catch_idx) {
|
| - CatchBlockEntryInstr* catch_entry = catch_entries[catch_idx];
|
| -
|
| - // Initialize cdefs with the original initial definitions (ParameterInstr).
|
| - // The following representation is used:
|
| - // ParameterInstr => unknown
|
| - // ConstantInstr => known constant
|
| - // NULL => non-constant
|
| - GrowableArray<Definition*>* idefs = catch_entry->initial_definitions();
|
| - GrowableArray<Definition*> cdefs(idefs->length());
|
| - cdefs.AddArray(*idefs);
|
| -
|
| - // exception_var and stacktrace_var are never constant.
|
| - intptr_t ex_idx = base - catch_entry->exception_var().index();
|
| - intptr_t st_idx = base - catch_entry->stacktrace_var().index();
|
| - cdefs[ex_idx] = cdefs[st_idx] = NULL;
|
| -
|
| - for (BlockIterator block_it = flow_graph->reverse_postorder_iterator();
|
| - !block_it.Done();
|
| - block_it.Advance()) {
|
| - BlockEntryInstr* block = block_it.Current();
|
| - if (block->try_index() == catch_entry->catch_try_index()) {
|
| - for (ForwardInstructionIterator instr_it(block);
|
| - !instr_it.Done();
|
| - instr_it.Advance()) {
|
| - Instruction* current = instr_it.Current();
|
| - if (current->MayThrow()) {
|
| - Environment* env = current->env()->Outermost();
|
| - ASSERT(env != NULL);
|
| - for (intptr_t env_idx = 0; env_idx < cdefs.length(); ++env_idx) {
|
| - if (cdefs[env_idx] != NULL &&
|
| - env->ValueAt(env_idx)->BindsToConstant()) {
|
| - cdefs[env_idx] = env->ValueAt(env_idx)->definition();
|
| - }
|
| - if (cdefs[env_idx] != env->ValueAt(env_idx)->definition()) {
|
| - cdefs[env_idx] = NULL;
|
| - }
|
| - }
|
| - }
|
| - }
|
| - }
|
| - }
|
| - for (intptr_t j = 0; j < idefs->length(); ++j) {
|
| - if (cdefs[j] != NULL && cdefs[j]->IsConstant()) {
|
| - // TODO(fschneider): Use constants from the constant pool.
|
| - Definition* old = (*idefs)[j];
|
| - ConstantInstr* orig = cdefs[j]->AsConstant();
|
| - ConstantInstr* copy =
|
| - new(flow_graph->zone()) ConstantInstr(orig->value());
|
| - copy->set_ssa_temp_index(flow_graph->alloc_ssa_temp_index());
|
| - old->ReplaceUsesWith(copy);
|
| - (*idefs)[j] = copy;
|
| - }
|
| - }
|
| - }
|
| -}
|
| -
|
| -
|
| -LICM::LICM(FlowGraph* flow_graph) : flow_graph_(flow_graph) {
|
| - ASSERT(flow_graph->is_licm_allowed());
|
| -}
|
| -
|
| -
|
| -void LICM::Hoist(ForwardInstructionIterator* it,
|
| - BlockEntryInstr* pre_header,
|
| - Instruction* current) {
|
| - if (current->IsCheckClass()) {
|
| - current->AsCheckClass()->set_licm_hoisted(true);
|
| - } else if (current->IsCheckSmi()) {
|
| - current->AsCheckSmi()->set_licm_hoisted(true);
|
| - } else if (current->IsCheckEitherNonSmi()) {
|
| - current->AsCheckEitherNonSmi()->set_licm_hoisted(true);
|
| - } else if (current->IsCheckArrayBound()) {
|
| - current->AsCheckArrayBound()->set_licm_hoisted(true);
|
| - }
|
| - if (FLAG_trace_optimization) {
|
| - THR_Print("Hoisting instruction %s:%" Pd " from B%" Pd " to B%" Pd "\n",
|
| - current->DebugName(),
|
| - current->GetDeoptId(),
|
| - current->GetBlock()->block_id(),
|
| - pre_header->block_id());
|
| - }
|
| - // Move the instruction out of the loop.
|
| - current->RemoveEnvironment();
|
| - if (it != NULL) {
|
| - it->RemoveCurrentFromGraph();
|
| - } else {
|
| - current->RemoveFromGraph();
|
| - }
|
| - GotoInstr* last = pre_header->last_instruction()->AsGoto();
|
| - // Using kind kEffect will not assign a fresh ssa temporary index.
|
| - flow_graph()->InsertBefore(last, current, last->env(), FlowGraph::kEffect);
|
| - current->CopyDeoptIdFrom(*last);
|
| -}
|
| -
|
| -
|
| -void LICM::TrySpecializeSmiPhi(PhiInstr* phi,
|
| - BlockEntryInstr* header,
|
| - BlockEntryInstr* pre_header) {
|
| - if (phi->Type()->ToCid() == kSmiCid) {
|
| - return;
|
| - }
|
| -
|
| - // Check if there is only a single kDynamicCid input to the phi that
|
| - // comes from the pre-header.
|
| - const intptr_t kNotFound = -1;
|
| - intptr_t non_smi_input = kNotFound;
|
| - for (intptr_t i = 0; i < phi->InputCount(); ++i) {
|
| - Value* input = phi->InputAt(i);
|
| - if (input->Type()->ToCid() != kSmiCid) {
|
| - if ((non_smi_input != kNotFound) ||
|
| - (input->Type()->ToCid() != kDynamicCid)) {
|
| - // There are multiple kDynamicCid inputs or there is an input that is
|
| - // known to be non-smi.
|
| - return;
|
| - } else {
|
| - non_smi_input = i;
|
| - }
|
| - }
|
| - }
|
| -
|
| - if ((non_smi_input == kNotFound) ||
|
| - (phi->block()->PredecessorAt(non_smi_input) != pre_header)) {
|
| - return;
|
| - }
|
| -
|
| - CheckSmiInstr* check = NULL;
|
| - for (Value* use = phi->input_use_list();
|
| - (use != NULL) && (check == NULL);
|
| - use = use->next_use()) {
|
| - check = use->instruction()->AsCheckSmi();
|
| - }
|
| -
|
| - if (check == NULL) {
|
| - return;
|
| - }
|
| -
|
| - // Host CheckSmi instruction and make this phi smi one.
|
| - Hoist(NULL, pre_header, check);
|
| -
|
| - // Replace value we are checking with phi's input.
|
| - check->value()->BindTo(phi->InputAt(non_smi_input)->definition());
|
| -
|
| - phi->UpdateType(CompileType::FromCid(kSmiCid));
|
| -}
|
| -
|
| -
|
| -// Load instructions handled by load elimination.
|
| -static bool IsLoadEliminationCandidate(Instruction* instr) {
|
| - return instr->IsLoadField()
|
| - || instr->IsLoadIndexed()
|
| - || instr->IsLoadStaticField();
|
| -}
|
| -
|
| -
|
| -static bool IsLoopInvariantLoad(ZoneGrowableArray<BitVector*>* sets,
|
| - intptr_t loop_header_index,
|
| - Instruction* instr) {
|
| - return IsLoadEliminationCandidate(instr) &&
|
| - (sets != NULL) &&
|
| - instr->HasPlaceId() &&
|
| - ((*sets)[loop_header_index] != NULL) &&
|
| - (*sets)[loop_header_index]->Contains(instr->place_id());
|
| -}
|
| -
|
| -
|
| -void LICM::OptimisticallySpecializeSmiPhis() {
|
| - if (!flow_graph()->function().allows_hoisting_check_class() ||
|
| - FLAG_precompilation) {
|
| - // Do not hoist any: Either deoptimized on a hoisted check,
|
| - // or compiling precompiled code where we can't do optimistic
|
| - // hoisting of checks.
|
| - return;
|
| - }
|
| -
|
| - const ZoneGrowableArray<BlockEntryInstr*>& loop_headers =
|
| - flow_graph()->LoopHeaders();
|
| -
|
| - for (intptr_t i = 0; i < loop_headers.length(); ++i) {
|
| - JoinEntryInstr* header = loop_headers[i]->AsJoinEntry();
|
| - // Skip loop that don't have a pre-header block.
|
| - BlockEntryInstr* pre_header = header->ImmediateDominator();
|
| - if (pre_header == NULL) continue;
|
| -
|
| - for (PhiIterator it(header); !it.Done(); it.Advance()) {
|
| - TrySpecializeSmiPhi(it.Current(), header, pre_header);
|
| - }
|
| - }
|
| -}
|
| -
|
| -
|
| -void LICM::Optimize() {
|
| - if (!flow_graph()->function().allows_hoisting_check_class()) {
|
| - // Do not hoist any.
|
| - return;
|
| - }
|
| -
|
| - const ZoneGrowableArray<BlockEntryInstr*>& loop_headers =
|
| - flow_graph()->LoopHeaders();
|
| -
|
| - ZoneGrowableArray<BitVector*>* loop_invariant_loads =
|
| - flow_graph()->loop_invariant_loads();
|
| -
|
| - BlockEffects* block_effects = flow_graph()->block_effects();
|
| -
|
| - for (intptr_t i = 0; i < loop_headers.length(); ++i) {
|
| - BlockEntryInstr* header = loop_headers[i];
|
| - // Skip loop that don't have a pre-header block.
|
| - BlockEntryInstr* pre_header = header->ImmediateDominator();
|
| - if (pre_header == NULL) continue;
|
| -
|
| - for (BitVector::Iterator loop_it(header->loop_info());
|
| - !loop_it.Done();
|
| - loop_it.Advance()) {
|
| - BlockEntryInstr* block = flow_graph()->preorder()[loop_it.Current()];
|
| - for (ForwardInstructionIterator it(block);
|
| - !it.Done();
|
| - it.Advance()) {
|
| - Instruction* current = it.Current();
|
| - if ((current->AllowsCSE() &&
|
| - block_effects->CanBeMovedTo(current, pre_header)) ||
|
| - IsLoopInvariantLoad(loop_invariant_loads, i, current)) {
|
| - bool inputs_loop_invariant = true;
|
| - for (int i = 0; i < current->InputCount(); ++i) {
|
| - Definition* input_def = current->InputAt(i)->definition();
|
| - if (!input_def->GetBlock()->Dominates(pre_header)) {
|
| - inputs_loop_invariant = false;
|
| - break;
|
| - }
|
| - }
|
| - if (inputs_loop_invariant &&
|
| - !current->IsAssertAssignable() &&
|
| - !current->IsAssertBoolean()) {
|
| - // TODO(fschneider): Enable hoisting of Assert-instructions
|
| - // if it safe to do.
|
| - Hoist(&it, pre_header, current);
|
| - }
|
| - }
|
| - }
|
| - }
|
| - }
|
| -}
|
| -
|
| -
|
| -// Place describes an abstract location (e.g. field) that IR can load
|
| -// from or store to.
|
| -//
|
| -// Places are also used to describe wild-card locations also known as aliases,
|
| -// that essentially represent sets of places that alias each other. Places A
|
| -// and B are said to alias each other if store into A can affect load from B.
|
| -//
|
| -// We distinguish the following aliases:
|
| -//
|
| -// - for fields
|
| -// - *.f, *.@offs - field inside some object;
|
| -// - X.f, X.@offs - field inside an allocated object X;
|
| -// - for indexed accesses
|
| -// - *[*] - non-constant index inside some object;
|
| -// - *[C] - constant index inside some object;
|
| -// - X[*] - non-constant index inside an allocated object X;
|
| -// - X[C] - constant index inside an allocated object X.
|
| -//
|
| -// Constant indexed places are divided into two subcategories:
|
| -//
|
| -// - Access to homogeneous array-like objects: Array, ImmutableArray,
|
| -// OneByteString, TwoByteString. These objects can only be accessed
|
| -// on element by element basis with all elements having the same size.
|
| -// This means X[C] aliases X[K] if and only if C === K.
|
| -// - TypedData accesses. TypedData allow to read one of the primitive
|
| -// data types at the given byte offset. When TypedData is accessed through
|
| -// index operator on a typed array or a typed array view it is guaranteed
|
| -// that the byte offset is always aligned by the element size. We write
|
| -// these accesses as X[C|S], where C is constant byte offset and S is size
|
| -// of the data type. Obviously X[C|S] and X[K|U] alias if and only if either
|
| -// C = RoundDown(K, S) or K = RoundDown(C, U).
|
| -// Note that not all accesses to typed data are aligned: e.g. ByteData
|
| -// allows unanaligned access through it's get*/set* methods.
|
| -// Check in Place::SetIndex ensures that we never create a place X[C|S]
|
| -// such that C is not aligned by S.
|
| -//
|
| -// Separating allocations from other objects improves precision of the
|
| -// load forwarding pass because of the following two properties:
|
| -//
|
| -// - if X can be proven to have no aliases itself (i.e. there is no other SSA
|
| -// variable that points to X) then no place inside X can be aliased with any
|
| -// wildcard dependent place (*.f, *.@offs, *[*], *[C]);
|
| -// - given allocations X and Y no place inside X can be aliased with any place
|
| -// inside Y even if any of them or both escape.
|
| -//
|
| -// It important to realize that single place can belong to multiple aliases.
|
| -// For example place X.f with aliased allocation X belongs both to X.f and *.f
|
| -// aliases. Likewise X[C] with non-aliased allocation X belongs to X[C] and X[*]
|
| -// aliases.
|
| -//
|
| -class Place : public ValueObject {
|
| - public:
|
| - enum Kind {
|
| - kNone,
|
| -
|
| - // Field location. For instance fields is represented as a pair of a Field
|
| - // object and an instance (SSA definition) that is being accessed.
|
| - // For static fields instance is NULL.
|
| - kField,
|
| -
|
| - // VMField location. Represented as a pair of an instance (SSA definition)
|
| - // being accessed and offset to the field.
|
| - kVMField,
|
| -
|
| - // Indexed location with a non-constant index.
|
| - kIndexed,
|
| -
|
| - // Indexed location with a constant index.
|
| - kConstantIndexed,
|
| - };
|
| -
|
| - // Size of the element accessed by constant index. Size is only important
|
| - // for TypedData because those accesses can alias even when constant indexes
|
| - // are not the same: X[0|4] aliases X[0|2] and X[2|2].
|
| - enum ElementSize {
|
| - // If indexed access is not a TypedData access then element size is not
|
| - // important because there is only a single possible access size depending
|
| - // on the receiver - X[C] aliases X[K] if and only if C == K.
|
| - // This is the size set for Array, ImmutableArray, OneByteString and
|
| - // TwoByteString accesses.
|
| - kNoSize,
|
| -
|
| - // 1 byte (Int8List, Uint8List, Uint8ClampedList).
|
| - kInt8,
|
| -
|
| - // 2 bytes (Int16List, Uint16List).
|
| - kInt16,
|
| -
|
| - // 4 bytes (Int32List, Uint32List, Float32List).
|
| - kInt32,
|
| -
|
| - // 8 bytes (Int64List, Uint64List, Float64List).
|
| - kInt64,
|
| -
|
| - // 16 bytes (Int32x4List, Float32x4List, Float64x2List).
|
| - kInt128,
|
| -
|
| - kLargestElementSize = kInt128,
|
| - };
|
| -
|
| - Place(const Place& other)
|
| - : ValueObject(),
|
| - flags_(other.flags_),
|
| - instance_(other.instance_),
|
| - raw_selector_(other.raw_selector_),
|
| - id_(other.id_) {
|
| - }
|
| -
|
| - // Construct a place from instruction if instruction accesses any place.
|
| - // Otherwise constructs kNone place.
|
| - Place(Instruction* instr, bool* is_load, bool* is_store)
|
| - : flags_(0),
|
| - instance_(NULL),
|
| - raw_selector_(0),
|
| - id_(0) {
|
| - switch (instr->tag()) {
|
| - case Instruction::kLoadField: {
|
| - LoadFieldInstr* load_field = instr->AsLoadField();
|
| - set_representation(load_field->representation());
|
| - instance_ = load_field->instance()->definition()->OriginalDefinition();
|
| - if (load_field->field() != NULL) {
|
| - set_kind(kField);
|
| - field_ = load_field->field();
|
| - } else {
|
| - set_kind(kVMField);
|
| - offset_in_bytes_ = load_field->offset_in_bytes();
|
| - }
|
| - *is_load = true;
|
| - break;
|
| - }
|
| -
|
| - case Instruction::kStoreInstanceField: {
|
| - StoreInstanceFieldInstr* store =
|
| - instr->AsStoreInstanceField();
|
| - set_representation(store->RequiredInputRepresentation(
|
| - StoreInstanceFieldInstr::kValuePos));
|
| - instance_ = store->instance()->definition()->OriginalDefinition();
|
| - if (!store->field().IsNull()) {
|
| - set_kind(kField);
|
| - field_ = &store->field();
|
| - } else {
|
| - set_kind(kVMField);
|
| - offset_in_bytes_ = store->offset_in_bytes();
|
| - }
|
| - *is_store = true;
|
| - break;
|
| - }
|
| -
|
| - case Instruction::kLoadStaticField:
|
| - set_kind(kField);
|
| - set_representation(instr->AsLoadStaticField()->representation());
|
| - field_ = &instr->AsLoadStaticField()->StaticField();
|
| - *is_load = true;
|
| - break;
|
| -
|
| - case Instruction::kStoreStaticField:
|
| - set_kind(kField);
|
| - set_representation(instr->AsStoreStaticField()->
|
| - RequiredInputRepresentation(StoreStaticFieldInstr::kValuePos));
|
| - field_ = &instr->AsStoreStaticField()->field();
|
| - *is_store = true;
|
| - break;
|
| -
|
| - case Instruction::kLoadIndexed: {
|
| - LoadIndexedInstr* load_indexed = instr->AsLoadIndexed();
|
| - set_representation(load_indexed->representation());
|
| - instance_ = load_indexed->array()->definition()->OriginalDefinition();
|
| - SetIndex(load_indexed->index()->definition(),
|
| - load_indexed->index_scale(),
|
| - load_indexed->class_id());
|
| - *is_load = true;
|
| - break;
|
| - }
|
| -
|
| - case Instruction::kStoreIndexed: {
|
| - StoreIndexedInstr* store_indexed = instr->AsStoreIndexed();
|
| - set_representation(store_indexed->
|
| - RequiredInputRepresentation(StoreIndexedInstr::kValuePos));
|
| - instance_ = store_indexed->array()->definition()->OriginalDefinition();
|
| - SetIndex(store_indexed->index()->definition(),
|
| - store_indexed->index_scale(),
|
| - store_indexed->class_id());
|
| - *is_store = true;
|
| - break;
|
| - }
|
| -
|
| - default:
|
| - break;
|
| - }
|
| - }
|
| -
|
| - // Create object representing *[*] alias.
|
| - static Place* CreateAnyInstanceAnyIndexAlias(Zone* zone,
|
| - intptr_t id) {
|
| - return Wrap(zone, Place(
|
| - EncodeFlags(kIndexed, kNoRepresentation, kNoSize),
|
| - NULL,
|
| - 0), id);
|
| - }
|
| -
|
| - // Return least generic alias for this place. Given that aliases are
|
| - // essentially sets of places we define least generic alias as a smallest
|
| - // alias that contains this place.
|
| - //
|
| - // We obtain such alias by a simple transformation:
|
| - //
|
| - // - for places that depend on an instance X.f, X.@offs, X[i], X[C]
|
| - // we drop X if X is not an allocation because in this case X does not
|
| - // posess an identity obtaining aliases *.f, *.@offs, *[i] and *[C]
|
| - // respectively;
|
| - // - for non-constant indexed places X[i] we drop information about the
|
| - // index obtaining alias X[*].
|
| - // - we drop information about representation, but keep element size
|
| - // if any.
|
| - //
|
| - Place ToAlias() const {
|
| - return Place(
|
| - RepresentationBits::update(kNoRepresentation, flags_),
|
| - (DependsOnInstance() && IsAllocation(instance())) ? instance() : NULL,
|
| - (kind() == kIndexed) ? 0 : raw_selector_);
|
| - }
|
| -
|
| - bool DependsOnInstance() const {
|
| - switch (kind()) {
|
| - case kField:
|
| - case kVMField:
|
| - case kIndexed:
|
| - case kConstantIndexed:
|
| - return true;
|
| -
|
| - case kNone:
|
| - return false;
|
| - }
|
| -
|
| - UNREACHABLE();
|
| - return false;
|
| - }
|
| -
|
| - // Given instance dependent alias X.f, X.@offs, X[C], X[*] return
|
| - // wild-card dependent alias *.f, *.@offs, *[C] or *[*] respectively.
|
| - Place CopyWithoutInstance() const {
|
| - ASSERT(DependsOnInstance());
|
| - return Place(flags_, NULL, raw_selector_);
|
| - }
|
| -
|
| - // Given alias X[C] or *[C] return X[*] and *[*] respectively.
|
| - Place CopyWithoutIndex() const {
|
| - ASSERT(kind() == kConstantIndexed);
|
| - return Place(EncodeFlags(kIndexed, kNoRepresentation, kNoSize),
|
| - instance_,
|
| - 0);
|
| - }
|
| -
|
| - // Given alias X[ByteOffs|S] and a larger element size S', return
|
| - // alias X[RoundDown(ByteOffs, S')|S'] - this is the byte offset of a larger
|
| - // typed array element that contains this typed array element.
|
| - // In other words this method computes the only possible place with the given
|
| - // size that can alias this place (due to alignment restrictions).
|
| - // For example for X[9|kInt8] and target size kInt32 we would return
|
| - // X[8|kInt32].
|
| - Place ToLargerElement(ElementSize to) const {
|
| - ASSERT(kind() == kConstantIndexed);
|
| - ASSERT(element_size() != kNoSize);
|
| - ASSERT(element_size() < to);
|
| - return Place(ElementSizeBits::update(to, flags_),
|
| - instance_,
|
| - RoundByteOffset(to, index_constant_));
|
| - }
|
| -
|
| -
|
| - intptr_t id() const { return id_; }
|
| -
|
| - Kind kind() const { return KindBits::decode(flags_); }
|
| -
|
| - Representation representation() const {
|
| - return RepresentationBits::decode(flags_);
|
| - }
|
| -
|
| - Definition* instance() const {
|
| - ASSERT(DependsOnInstance());
|
| - return instance_;
|
| - }
|
| -
|
| - void set_instance(Definition* def) {
|
| - ASSERT(DependsOnInstance());
|
| - instance_ = def->OriginalDefinition();
|
| - }
|
| -
|
| - const Field& field() const {
|
| - ASSERT(kind() == kField);
|
| - return *field_;
|
| - }
|
| -
|
| - intptr_t offset_in_bytes() const {
|
| - ASSERT(kind() == kVMField);
|
| - return offset_in_bytes_;
|
| - }
|
| -
|
| - Definition* index() const {
|
| - ASSERT(kind() == kIndexed);
|
| - return index_;
|
| - }
|
| -
|
| - ElementSize element_size() const {
|
| - return ElementSizeBits::decode(flags_);
|
| - }
|
| -
|
| - intptr_t index_constant() const {
|
| - ASSERT(kind() == kConstantIndexed);
|
| - return index_constant_;
|
| - }
|
| -
|
| - static const char* DefinitionName(Definition* def) {
|
| - if (def == NULL) {
|
| - return "*";
|
| - } else {
|
| - return Thread::Current()->zone()->PrintToString(
|
| - "v%" Pd, def->ssa_temp_index());
|
| - }
|
| - }
|
| -
|
| - const char* ToCString() const {
|
| - switch (kind()) {
|
| - case kNone:
|
| - return "<none>";
|
| -
|
| - case kField: {
|
| - const char* field_name = String::Handle(field().name()).ToCString();
|
| - if (field().is_static()) {
|
| - return Thread::Current()->zone()->PrintToString(
|
| - "<%s>", field_name);
|
| - } else {
|
| - return Thread::Current()->zone()->PrintToString(
|
| - "<%s.%s>", DefinitionName(instance()), field_name);
|
| - }
|
| - }
|
| -
|
| - case kVMField:
|
| - return Thread::Current()->zone()->PrintToString(
|
| - "<%s.@%" Pd ">",
|
| - DefinitionName(instance()),
|
| - offset_in_bytes());
|
| -
|
| - case kIndexed:
|
| - return Thread::Current()->zone()->PrintToString(
|
| - "<%s[%s]>",
|
| - DefinitionName(instance()),
|
| - DefinitionName(index()));
|
| -
|
| - case kConstantIndexed:
|
| - if (element_size() == kNoSize) {
|
| - return Thread::Current()->zone()->PrintToString(
|
| - "<%s[%" Pd "]>",
|
| - DefinitionName(instance()),
|
| - index_constant());
|
| - } else {
|
| - return Thread::Current()->zone()->PrintToString(
|
| - "<%s[%" Pd "|%" Pd "]>",
|
| - DefinitionName(instance()),
|
| - index_constant(),
|
| - ElementSizeMultiplier(element_size()));
|
| - }
|
| - }
|
| - UNREACHABLE();
|
| - return "<?>";
|
| - }
|
| -
|
| - // Fields that are considered immutable by load optimization.
|
| - // Handle static finals as non-final with precompilation because
|
| - // they may be reset to uninitialized after compilation.
|
| - bool IsImmutableField() const {
|
| - return (kind() == kField)
|
| - && field().is_final()
|
| - && (!field().is_static() || !FLAG_fields_may_be_reset);
|
| - }
|
| -
|
| - intptr_t Hashcode() const {
|
| - return (flags_ * 63 + reinterpret_cast<intptr_t>(instance_)) * 31 +
|
| - FieldHashcode();
|
| - }
|
| -
|
| - bool Equals(const Place* other) const {
|
| - return (flags_ == other->flags_) &&
|
| - (instance_ == other->instance_) &&
|
| - SameField(other);
|
| - }
|
| -
|
| - // Create a zone allocated copy of this place and assign given id to it.
|
| - static Place* Wrap(Zone* zone, const Place& place, intptr_t id);
|
| -
|
| - static bool IsAllocation(Definition* defn) {
|
| - return (defn != NULL) &&
|
| - (defn->IsAllocateObject() ||
|
| - defn->IsCreateArray() ||
|
| - defn->IsAllocateUninitializedContext() ||
|
| - (defn->IsStaticCall() &&
|
| - defn->AsStaticCall()->IsRecognizedFactory()));
|
| - }
|
| -
|
| - private:
|
| - Place(uword flags, Definition* instance, intptr_t selector)
|
| - : flags_(flags),
|
| - instance_(instance),
|
| - raw_selector_(selector),
|
| - id_(0) {
|
| - }
|
| -
|
| - bool SameField(const Place* other) const {
|
| - return (kind() == kField) ? (field().raw() == other->field().raw())
|
| - : (offset_in_bytes_ == other->offset_in_bytes_);
|
| - }
|
| -
|
| - intptr_t FieldHashcode() const {
|
| - return (kind() == kField) ? reinterpret_cast<intptr_t>(field().raw())
|
| - : offset_in_bytes_;
|
| - }
|
| -
|
| - void set_representation(Representation rep) {
|
| - flags_ = RepresentationBits::update(rep, flags_);
|
| - }
|
| -
|
| - void set_kind(Kind kind) {
|
| - flags_ = KindBits::update(kind, flags_);
|
| - }
|
| -
|
| - void set_element_size(ElementSize scale) {
|
| - flags_ = ElementSizeBits::update(scale, flags_);
|
| - }
|
| -
|
| - void SetIndex(Definition* index, intptr_t scale, intptr_t class_id) {
|
| - ConstantInstr* index_constant = index->AsConstant();
|
| - if ((index_constant != NULL) && index_constant->value().IsSmi()) {
|
| - const intptr_t index_value = Smi::Cast(index_constant->value()).Value();
|
| - const ElementSize size = ElementSizeFor(class_id);
|
| - const bool is_typed_data = (size != kNoSize);
|
| -
|
| - // If we are writing into the typed data scale the index to
|
| - // get byte offset. Otherwise ignore the scale.
|
| - if (!is_typed_data) {
|
| - scale = 1;
|
| - }
|
| +void FlowGraphOptimizer::EliminateEnvironments() {
|
| + // After this pass we can no longer perform LICM and hoist instructions
|
| + // that can deoptimize.
|
|
|
| - // Guard against potential multiplication overflow and negative indices.
|
| - if ((0 <= index_value) && (index_value < (kMaxInt32 / scale))) {
|
| - const intptr_t scaled_index = index_value * scale;
|
| -
|
| - // Guard against unaligned byte offsets.
|
| - if (!is_typed_data ||
|
| - Utils::IsAligned(scaled_index, ElementSizeMultiplier(size))) {
|
| - set_kind(kConstantIndexed);
|
| - set_element_size(size);
|
| - index_constant_ = scaled_index;
|
| - return;
|
| - }
|
| + flow_graph_->disallow_licm();
|
| + for (intptr_t i = 0; i < block_order_.length(); ++i) {
|
| + BlockEntryInstr* block = block_order_[i];
|
| + block->RemoveEnvironment();
|
| + for (ForwardInstructionIterator it(block); !it.Done(); it.Advance()) {
|
| + Instruction* current = it.Current();
|
| + if (!current->CanDeoptimize()) {
|
| + // TODO(srdjan): --source-lines needs deopt environments to get at
|
| + // the code for this instruction, however, leaving the environment
|
| + // changes code.
|
| + current->RemoveEnvironment();
|
| }
|
| -
|
| - // Fallthrough: create generic _[*] place.
|
| - }
|
| -
|
| - set_kind(kIndexed);
|
| - index_ = index;
|
| - }
|
| -
|
| - static uword EncodeFlags(Kind kind, Representation rep, ElementSize scale) {
|
| - ASSERT((kind == kConstantIndexed) || (scale == kNoSize));
|
| - return KindBits::encode(kind) |
|
| - RepresentationBits::encode(rep) |
|
| - ElementSizeBits::encode(scale);
|
| - }
|
| -
|
| - static ElementSize ElementSizeFor(intptr_t class_id) {
|
| - switch (class_id) {
|
| - case kArrayCid:
|
| - case kImmutableArrayCid:
|
| - case kOneByteStringCid:
|
| - case kTwoByteStringCid:
|
| - // Object arrays and strings do not allow accessing them through
|
| - // different types. No need to attach scale.
|
| - return kNoSize;
|
| -
|
| - case kTypedDataInt8ArrayCid:
|
| - case kTypedDataUint8ArrayCid:
|
| - case kTypedDataUint8ClampedArrayCid:
|
| - case kExternalTypedDataUint8ArrayCid:
|
| - case kExternalTypedDataUint8ClampedArrayCid:
|
| - return kInt8;
|
| -
|
| - case kTypedDataInt16ArrayCid:
|
| - case kTypedDataUint16ArrayCid:
|
| - return kInt16;
|
| -
|
| - case kTypedDataInt32ArrayCid:
|
| - case kTypedDataUint32ArrayCid:
|
| - case kTypedDataFloat32ArrayCid:
|
| - return kInt32;
|
| -
|
| - case kTypedDataInt64ArrayCid:
|
| - case kTypedDataUint64ArrayCid:
|
| - case kTypedDataFloat64ArrayCid:
|
| - return kInt64;
|
| -
|
| - case kTypedDataInt32x4ArrayCid:
|
| - case kTypedDataFloat32x4ArrayCid:
|
| - case kTypedDataFloat64x2ArrayCid:
|
| - return kInt128;
|
| -
|
| - default:
|
| - UNREACHABLE();
|
| - return kNoSize;
|
| }
|
| }
|
| -
|
| - static intptr_t ElementSizeMultiplier(ElementSize size) {
|
| - return 1 << (static_cast<intptr_t>(size) - static_cast<intptr_t>(kInt8));
|
| - }
|
| -
|
| - static intptr_t RoundByteOffset(ElementSize size, intptr_t offset) {
|
| - return offset & ~(ElementSizeMultiplier(size) - 1);
|
| - }
|
| -
|
| - class KindBits : public BitField<uword, Kind, 0, 3> {};
|
| - class RepresentationBits :
|
| - public BitField<uword, Representation, KindBits::kNextBit, 11> {};
|
| - class ElementSizeBits :
|
| - public BitField<uword, ElementSize, RepresentationBits::kNextBit, 3> {};
|
| -
|
| - uword flags_;
|
| - Definition* instance_;
|
| - union {
|
| - intptr_t raw_selector_;
|
| - const Field* field_;
|
| - intptr_t offset_in_bytes_;
|
| - intptr_t index_constant_;
|
| - Definition* index_;
|
| - };
|
| -
|
| - intptr_t id_;
|
| -};
|
| -
|
| -
|
| -class ZonePlace : public ZoneAllocated {
|
| - public:
|
| - explicit ZonePlace(const Place& place) : place_(place) { }
|
| -
|
| - Place* place() { return &place_; }
|
| -
|
| - private:
|
| - Place place_;
|
| -};
|
| -
|
| -
|
| -Place* Place::Wrap(Zone* zone, const Place& place, intptr_t id) {
|
| - Place* wrapped = (new(zone) ZonePlace(place))->place();
|
| - wrapped->id_ = id;
|
| - return wrapped;
|
| -}
|
| -
|
| -
|
| -// Correspondence between places connected through outgoing phi moves on the
|
| -// edge that targets join.
|
| -class PhiPlaceMoves : public ZoneAllocated {
|
| - public:
|
| - // Record a move from the place with id |from| to the place with id |to| at
|
| - // the given block.
|
| - void CreateOutgoingMove(Zone* zone,
|
| - BlockEntryInstr* block, intptr_t from, intptr_t to) {
|
| - const intptr_t block_num = block->preorder_number();
|
| - while (moves_.length() <= block_num) {
|
| - moves_.Add(NULL);
|
| - }
|
| -
|
| - if (moves_[block_num] == NULL) {
|
| - moves_[block_num] = new(zone) ZoneGrowableArray<Move>(5);
|
| - }
|
| -
|
| - moves_[block_num]->Add(Move(from, to));
|
| - }
|
| -
|
| - class Move {
|
| - public:
|
| - Move(intptr_t from, intptr_t to) : from_(from), to_(to) { }
|
| -
|
| - intptr_t from() const { return from_; }
|
| - intptr_t to() const { return to_; }
|
| -
|
| - private:
|
| - intptr_t from_;
|
| - intptr_t to_;
|
| - };
|
| -
|
| - typedef const ZoneGrowableArray<Move>* MovesList;
|
| -
|
| - MovesList GetOutgoingMoves(BlockEntryInstr* block) const {
|
| - const intptr_t block_num = block->preorder_number();
|
| - return (block_num < moves_.length()) ?
|
| - moves_[block_num] : NULL;
|
| - }
|
| -
|
| - private:
|
| - GrowableArray<ZoneGrowableArray<Move>* > moves_;
|
| -};
|
| -
|
| -
|
| -// A map from aliases to a set of places sharing the alias. Additionally
|
| -// carries a set of places that can be aliased by side-effects, essentially
|
| -// those that are affected by calls.
|
| -class AliasedSet : public ZoneAllocated {
|
| - public:
|
| - AliasedSet(Zone* zone,
|
| - DirectChainedHashMap<PointerKeyValueTrait<Place> >* places_map,
|
| - ZoneGrowableArray<Place*>* places,
|
| - PhiPlaceMoves* phi_moves)
|
| - : zone_(zone),
|
| - places_map_(places_map),
|
| - places_(*places),
|
| - phi_moves_(phi_moves),
|
| - aliases_(5),
|
| - aliases_map_(),
|
| - typed_data_access_sizes_(),
|
| - representatives_(),
|
| - killed_(),
|
| - aliased_by_effects_(new(zone) BitVector(zone, places->length())) {
|
| - InsertAlias(Place::CreateAnyInstanceAnyIndexAlias(zone_,
|
| - kAnyInstanceAnyIndexAlias));
|
| - for (intptr_t i = 0; i < places_.length(); i++) {
|
| - AddRepresentative(places_[i]);
|
| - }
|
| - ComputeKillSets();
|
| - }
|
| -
|
| - intptr_t LookupAliasId(const Place& alias) {
|
| - const Place* result = aliases_map_.Lookup(&alias);
|
| - return (result != NULL) ? result->id() : static_cast<intptr_t>(kNoAlias);
|
| - }
|
| -
|
| - BitVector* GetKilledSet(intptr_t alias) {
|
| - return (alias < killed_.length()) ? killed_[alias] : NULL;
|
| - }
|
| -
|
| - intptr_t max_place_id() const { return places().length(); }
|
| - bool IsEmpty() const { return max_place_id() == 0; }
|
| -
|
| - BitVector* aliased_by_effects() const { return aliased_by_effects_; }
|
| -
|
| - const ZoneGrowableArray<Place*>& places() const {
|
| - return places_;
|
| - }
|
| -
|
| - Place* LookupCanonical(Place* place) const {
|
| - return places_map_->Lookup(place);
|
| - }
|
| -
|
| - void PrintSet(BitVector* set) {
|
| - bool comma = false;
|
| - for (BitVector::Iterator it(set);
|
| - !it.Done();
|
| - it.Advance()) {
|
| - if (comma) {
|
| - THR_Print(", ");
|
| - }
|
| - THR_Print("%s", places_[it.Current()]->ToCString());
|
| - comma = true;
|
| - }
|
| - }
|
| -
|
| - const PhiPlaceMoves* phi_moves() const { return phi_moves_; }
|
| -
|
| - void RollbackAliasedIdentites() {
|
| - for (intptr_t i = 0; i < identity_rollback_.length(); ++i) {
|
| - identity_rollback_[i]->SetIdentity(AliasIdentity::Unknown());
|
| - }
|
| - }
|
| -
|
| - // Returns false if the result of an allocation instruction can't be aliased
|
| - // by another SSA variable and true otherwise.
|
| - bool CanBeAliased(Definition* alloc) {
|
| - if (!Place::IsAllocation(alloc)) {
|
| - return true;
|
| - }
|
| -
|
| - if (alloc->Identity().IsUnknown()) {
|
| - ComputeAliasing(alloc);
|
| - }
|
| -
|
| - return !alloc->Identity().IsNotAliased();
|
| - }
|
| -
|
| - enum {
|
| - kNoAlias = 0
|
| - };
|
| -
|
| - private:
|
| - enum {
|
| - // Artificial alias that is used to collect all representatives of the
|
| - // *[C], X[C] aliases for arbitrary C.
|
| - kAnyConstantIndexedAlias = 1,
|
| -
|
| - // Artificial alias that is used to collect all representatives of
|
| - // *[C] alias for arbitrary C.
|
| - kUnknownInstanceConstantIndexedAlias = 2,
|
| -
|
| - // Artificial alias that is used to collect all representatives of
|
| - // X[*] alias for all X.
|
| - kAnyAllocationIndexedAlias = 3,
|
| -
|
| - // *[*] alias.
|
| - kAnyInstanceAnyIndexAlias = 4
|
| - };
|
| -
|
| - // Compute least generic alias for the place and assign alias id to it.
|
| - void AddRepresentative(Place* place) {
|
| - if (!place->IsImmutableField()) {
|
| - const Place* alias = CanonicalizeAlias(place->ToAlias());
|
| - EnsureSet(&representatives_, alias->id())->Add(place->id());
|
| -
|
| - // Update cumulative representative sets that are used during
|
| - // killed sets computation.
|
| - if (alias->kind() == Place::kConstantIndexed) {
|
| - if (CanBeAliased(alias->instance())) {
|
| - EnsureSet(&representatives_, kAnyConstantIndexedAlias)->
|
| - Add(place->id());
|
| - }
|
| -
|
| - if (alias->instance() == NULL) {
|
| - EnsureSet(&representatives_, kUnknownInstanceConstantIndexedAlias)->
|
| - Add(place->id());
|
| - }
|
| -
|
| - // Collect all element sizes used to access TypedData arrays in
|
| - // the function. This is used to skip sizes without representatives
|
| - // when computing kill sets.
|
| - if (alias->element_size() != Place::kNoSize) {
|
| - typed_data_access_sizes_.Add(alias->element_size());
|
| - }
|
| - } else if ((alias->kind() == Place::kIndexed) &&
|
| - CanBeAliased(place->instance())) {
|
| - EnsureSet(&representatives_, kAnyAllocationIndexedAlias)->
|
| - Add(place->id());
|
| - }
|
| -
|
| - if (!IsIndependentFromEffects(place)) {
|
| - aliased_by_effects_->Add(place->id());
|
| - }
|
| - }
|
| - }
|
| -
|
| - void ComputeKillSets() {
|
| - for (intptr_t i = 0; i < aliases_.length(); ++i) {
|
| - const Place* alias = aliases_[i];
|
| - // Add all representatives to the kill set.
|
| - AddAllRepresentatives(alias->id(), alias->id());
|
| - ComputeKillSet(alias);
|
| - }
|
| -
|
| - if (FLAG_trace_load_optimization) {
|
| - THR_Print("Aliases KILL sets:\n");
|
| - for (intptr_t i = 0; i < aliases_.length(); ++i) {
|
| - const Place* alias = aliases_[i];
|
| - BitVector* kill = GetKilledSet(alias->id());
|
| -
|
| - THR_Print("%s: ", alias->ToCString());
|
| - if (kill != NULL) {
|
| - PrintSet(kill);
|
| - }
|
| - THR_Print("\n");
|
| - }
|
| - }
|
| - }
|
| -
|
| - void InsertAlias(const Place* alias) {
|
| - aliases_map_.Insert(alias);
|
| - aliases_.Add(alias);
|
| - }
|
| -
|
| - const Place* CanonicalizeAlias(const Place& alias) {
|
| - const Place* canonical = aliases_map_.Lookup(&alias);
|
| - if (canonical == NULL) {
|
| - canonical = Place::Wrap(zone_,
|
| - alias,
|
| - kAnyInstanceAnyIndexAlias + aliases_.length());
|
| - InsertAlias(canonical);
|
| - }
|
| - ASSERT(aliases_map_.Lookup(&alias) == canonical);
|
| - return canonical;
|
| - }
|
| -
|
| - BitVector* GetRepresentativesSet(intptr_t alias) {
|
| - return (alias < representatives_.length()) ? representatives_[alias] : NULL;
|
| - }
|
| -
|
| - BitVector* EnsureSet(GrowableArray<BitVector*>* sets,
|
| - intptr_t alias) {
|
| - while (sets->length() <= alias) {
|
| - sets->Add(NULL);
|
| - }
|
| -
|
| - BitVector* set = (*sets)[alias];
|
| - if (set == NULL) {
|
| - (*sets)[alias] = set = new(zone_) BitVector(zone_, max_place_id());
|
| - }
|
| - return set;
|
| - }
|
| -
|
| - void AddAllRepresentatives(const Place* to, intptr_t from) {
|
| - AddAllRepresentatives(to->id(), from);
|
| - }
|
| -
|
| - void AddAllRepresentatives(intptr_t to, intptr_t from) {
|
| - BitVector* from_set = GetRepresentativesSet(from);
|
| - if (from_set != NULL) {
|
| - EnsureSet(&killed_, to)->AddAll(from_set);
|
| - }
|
| - }
|
| -
|
| - void CrossAlias(const Place* to, const Place& from) {
|
| - const intptr_t from_id = LookupAliasId(from);
|
| - if (from_id == kNoAlias) {
|
| - return;
|
| - }
|
| - CrossAlias(to, from_id);
|
| - }
|
| -
|
| - void CrossAlias(const Place* to, intptr_t from) {
|
| - AddAllRepresentatives(to->id(), from);
|
| - AddAllRepresentatives(from, to->id());
|
| - }
|
| -
|
| - // When computing kill sets we let less generic alias insert its
|
| - // representatives into more generic alias'es kill set. For example
|
| - // when visiting alias X[*] instead of searching for all aliases X[C]
|
| - // and inserting their representatives into kill set for X[*] we update
|
| - // kill set for X[*] each time we visit new X[C] for some C.
|
| - // There is an exception however: if both aliases are parametric like *[C]
|
| - // and X[*] which cross alias when X is an aliased allocation then we use
|
| - // artificial aliases that contain all possible representatives for the given
|
| - // alias for any value of the parameter to compute resulting kill set.
|
| - void ComputeKillSet(const Place* alias) {
|
| - switch (alias->kind()) {
|
| - case Place::kIndexed: // Either *[*] or X[*] alias.
|
| - if (alias->instance() == NULL) {
|
| - // *[*] aliases with X[*], X[C], *[C].
|
| - AddAllRepresentatives(alias, kAnyConstantIndexedAlias);
|
| - AddAllRepresentatives(alias, kAnyAllocationIndexedAlias);
|
| - } else if (CanBeAliased(alias->instance())) {
|
| - // X[*] aliases with X[C].
|
| - // If X can be aliased then X[*] also aliases with *[C], *[*].
|
| - CrossAlias(alias, kAnyInstanceAnyIndexAlias);
|
| - AddAllRepresentatives(alias, kUnknownInstanceConstantIndexedAlias);
|
| - }
|
| - break;
|
| -
|
| - case Place::kConstantIndexed: // Either X[C] or *[C] alias.
|
| - if (alias->element_size() != Place::kNoSize) {
|
| - const bool has_aliased_instance =
|
| - (alias->instance() != NULL) && CanBeAliased(alias->instance());
|
| -
|
| - // If this is a TypedData access then X[C|S] aliases larger elements
|
| - // covering this one X[RoundDown(C, S')|S'] for all S' > S and
|
| - // all smaller elements being covered by this one X[C'|S'] for
|
| - // some S' < S and all C' such that C = RoundDown(C', S).
|
| - // In the loop below it's enough to only propagate aliasing to
|
| - // larger aliases because propagation is symmetric: smaller aliases
|
| - // (if there are any) would update kill set for this alias when they
|
| - // are visited.
|
| - for (intptr_t i = static_cast<intptr_t>(alias->element_size()) + 1;
|
| - i <= Place::kLargestElementSize;
|
| - i++) {
|
| - // Skip element sizes that a guaranteed to have no representatives.
|
| - if (!typed_data_access_sizes_.Contains(alias->element_size())) {
|
| - continue;
|
| - }
|
| -
|
| - // X[C|S] aliases with X[RoundDown(C, S')|S'] and likewise
|
| - // *[C|S] aliases with *[RoundDown(C, S')|S'].
|
| - const Place larger_alias =
|
| - alias->ToLargerElement(static_cast<Place::ElementSize>(i));
|
| - CrossAlias(alias, larger_alias);
|
| - if (has_aliased_instance) {
|
| - // If X is an aliased instance then X[C|S] aliases
|
| - // with *[RoundDown(C, S')|S'].
|
| - CrossAlias(alias, larger_alias.CopyWithoutInstance());
|
| - }
|
| - }
|
| - }
|
| -
|
| - if (alias->instance() == NULL) {
|
| - // *[C] aliases with X[C], X[*], *[*].
|
| - AddAllRepresentatives(alias, kAnyAllocationIndexedAlias);
|
| - CrossAlias(alias, kAnyInstanceAnyIndexAlias);
|
| - } else {
|
| - // X[C] aliases with X[*].
|
| - // If X can be aliased then X[C] also aliases with *[C], *[*].
|
| - CrossAlias(alias, alias->CopyWithoutIndex());
|
| - if (CanBeAliased(alias->instance())) {
|
| - CrossAlias(alias, alias->CopyWithoutInstance());
|
| - CrossAlias(alias, kAnyInstanceAnyIndexAlias);
|
| - }
|
| - }
|
| - break;
|
| -
|
| - case Place::kField:
|
| - case Place::kVMField:
|
| - if (CanBeAliased(alias->instance())) {
|
| - // X.f or X.@offs alias with *.f and *.@offs respectively.
|
| - CrossAlias(alias, alias->CopyWithoutInstance());
|
| - }
|
| - break;
|
| -
|
| - case Place::kNone:
|
| - UNREACHABLE();
|
| - }
|
| - }
|
| -
|
| - // Returns true if the given load is unaffected by external side-effects.
|
| - // This essentially means that no stores to the same location can
|
| - // occur in other functions.
|
| - bool IsIndependentFromEffects(Place* place) {
|
| - if (place->IsImmutableField()) {
|
| - // Note that we can't use LoadField's is_immutable attribute here because
|
| - // some VM-fields (those that have no corresponding Field object and
|
| - // accessed through offset alone) can share offset but have different
|
| - // immutability properties.
|
| - // One example is the length property of growable and fixed size list. If
|
| - // loads of these two properties occur in the same function for the same
|
| - // receiver then they will get the same expression number. However
|
| - // immutability of the length of fixed size list does not mean that
|
| - // growable list also has immutable property. Thus we will make a
|
| - // conservative assumption for the VM-properties.
|
| - // TODO(vegorov): disambiguate immutable and non-immutable VM-fields with
|
| - // the same offset e.g. through recognized kind.
|
| - return true;
|
| - }
|
| -
|
| - return ((place->kind() == Place::kField) ||
|
| - (place->kind() == Place::kVMField)) &&
|
| - !CanBeAliased(place->instance());
|
| - }
|
| -
|
| - // Returns true if there are direct loads from the given place.
|
| - bool HasLoadsFromPlace(Definition* defn, const Place* place) {
|
| - ASSERT((place->kind() == Place::kField) ||
|
| - (place->kind() == Place::kVMField));
|
| -
|
| - for (Value* use = defn->input_use_list();
|
| - use != NULL;
|
| - use = use->next_use()) {
|
| - Instruction* instr = use->instruction();
|
| - if ((instr->IsRedefinition() ||
|
| - instr->IsAssertAssignable()) &&
|
| - HasLoadsFromPlace(instr->AsDefinition(), place)) {
|
| - return true;
|
| - }
|
| - bool is_load = false, is_store;
|
| - Place load_place(instr, &is_load, &is_store);
|
| -
|
| - if (is_load && load_place.Equals(place)) {
|
| - return true;
|
| - }
|
| - }
|
| -
|
| - return false;
|
| - }
|
| -
|
| - // Check if any use of the definition can create an alias.
|
| - // Can add more objects into aliasing_worklist_.
|
| - bool AnyUseCreatesAlias(Definition* defn) {
|
| - for (Value* use = defn->input_use_list();
|
| - use != NULL;
|
| - use = use->next_use()) {
|
| - Instruction* instr = use->instruction();
|
| - if (instr->IsPushArgument() ||
|
| - (instr->IsStoreIndexed()
|
| - && (use->use_index() == StoreIndexedInstr::kValuePos)) ||
|
| - instr->IsStoreStaticField() ||
|
| - instr->IsPhi()) {
|
| - return true;
|
| - } else if ((instr->IsAssertAssignable() || instr->IsRedefinition()) &&
|
| - AnyUseCreatesAlias(instr->AsDefinition())) {
|
| - return true;
|
| - } else if ((instr->IsStoreInstanceField()
|
| - && (use->use_index() != StoreInstanceFieldInstr::kInstancePos))) {
|
| - ASSERT(use->use_index() == StoreInstanceFieldInstr::kValuePos);
|
| - // If we store this value into an object that is not aliased itself
|
| - // and we never load again then the store does not create an alias.
|
| - StoreInstanceFieldInstr* store = instr->AsStoreInstanceField();
|
| - Definition* instance =
|
| - store->instance()->definition()->OriginalDefinition();
|
| - if (Place::IsAllocation(instance) &&
|
| - !instance->Identity().IsAliased()) {
|
| - bool is_load, is_store;
|
| - Place store_place(instr, &is_load, &is_store);
|
| -
|
| - if (!HasLoadsFromPlace(instance, &store_place)) {
|
| - // No loads found that match this store. If it is yet unknown if
|
| - // the object is not aliased then optimistically assume this but
|
| - // add it to the worklist to check its uses transitively.
|
| - if (instance->Identity().IsUnknown()) {
|
| - instance->SetIdentity(AliasIdentity::NotAliased());
|
| - aliasing_worklist_.Add(instance);
|
| - }
|
| - continue;
|
| - }
|
| - }
|
| - return true;
|
| - }
|
| - }
|
| - return false;
|
| - }
|
| -
|
| - // Mark any value stored into the given object as potentially aliased.
|
| - void MarkStoredValuesEscaping(Definition* defn) {
|
| - // Find all stores into this object.
|
| - for (Value* use = defn->input_use_list();
|
| - use != NULL;
|
| - use = use->next_use()) {
|
| - if (use->instruction()->IsRedefinition() ||
|
| - use->instruction()->IsAssertAssignable()) {
|
| - MarkStoredValuesEscaping(use->instruction()->AsDefinition());
|
| - continue;
|
| - }
|
| - if ((use->use_index() == StoreInstanceFieldInstr::kInstancePos) &&
|
| - use->instruction()->IsStoreInstanceField()) {
|
| - StoreInstanceFieldInstr* store =
|
| - use->instruction()->AsStoreInstanceField();
|
| - Definition* value = store->value()->definition()->OriginalDefinition();
|
| - if (value->Identity().IsNotAliased()) {
|
| - value->SetIdentity(AliasIdentity::Aliased());
|
| - identity_rollback_.Add(value);
|
| -
|
| - // Add to worklist to propagate the mark transitively.
|
| - aliasing_worklist_.Add(value);
|
| - }
|
| - }
|
| - }
|
| - }
|
| -
|
| - // Determine if the given definition can't be aliased.
|
| - void ComputeAliasing(Definition* alloc) {
|
| - ASSERT(Place::IsAllocation(alloc));
|
| - ASSERT(alloc->Identity().IsUnknown());
|
| - ASSERT(aliasing_worklist_.is_empty());
|
| -
|
| - alloc->SetIdentity(AliasIdentity::NotAliased());
|
| - aliasing_worklist_.Add(alloc);
|
| -
|
| - while (!aliasing_worklist_.is_empty()) {
|
| - Definition* defn = aliasing_worklist_.RemoveLast();
|
| - ASSERT(Place::IsAllocation(defn));
|
| - // If the definition in the worklist was optimistically marked as
|
| - // not-aliased check that optimistic assumption still holds: check if
|
| - // any of its uses can create an alias.
|
| - if (!defn->Identity().IsAliased() && AnyUseCreatesAlias(defn)) {
|
| - defn->SetIdentity(AliasIdentity::Aliased());
|
| - identity_rollback_.Add(defn);
|
| - }
|
| -
|
| - // If the allocation site is marked as aliased conservatively mark
|
| - // any values stored into the object aliased too.
|
| - if (defn->Identity().IsAliased()) {
|
| - MarkStoredValuesEscaping(defn);
|
| - }
|
| - }
|
| - }
|
| -
|
| - Zone* zone_;
|
| -
|
| - DirectChainedHashMap<PointerKeyValueTrait<Place> >* places_map_;
|
| -
|
| - const ZoneGrowableArray<Place*>& places_;
|
| -
|
| - const PhiPlaceMoves* phi_moves_;
|
| -
|
| - // A list of all seen aliases and a map that allows looking up canonical
|
| - // alias object.
|
| - GrowableArray<const Place*> aliases_;
|
| - DirectChainedHashMap<PointerKeyValueTrait<const Place> > aliases_map_;
|
| -
|
| - SmallSet<Place::ElementSize> typed_data_access_sizes_;
|
| -
|
| - // Maps alias id to set of ids of places representing the alias.
|
| - // Place represents an alias if this alias is least generic alias for
|
| - // the place.
|
| - // (see ToAlias for the definition of least generic alias).
|
| - GrowableArray<BitVector*> representatives_;
|
| -
|
| - // Maps alias id to set of ids of places aliased.
|
| - GrowableArray<BitVector*> killed_;
|
| -
|
| - // Set of ids of places that can be affected by side-effects other than
|
| - // explicit stores (i.e. through calls).
|
| - BitVector* aliased_by_effects_;
|
| -
|
| - // Worklist used during alias analysis.
|
| - GrowableArray<Definition*> aliasing_worklist_;
|
| -
|
| - // List of definitions that had their identity set to Aliased. At the end
|
| - // of load optimization their identity will be rolled back to Unknown to
|
| - // avoid treating them as Aliased at later stages without checking first
|
| - // as optimizations can potentially eliminate instructions leading to
|
| - // aliasing.
|
| - GrowableArray<Definition*> identity_rollback_;
|
| -};
|
| -
|
| -
|
| -static Definition* GetStoredValue(Instruction* instr) {
|
| - if (instr->IsStoreIndexed()) {
|
| - return instr->AsStoreIndexed()->value()->definition();
|
| - }
|
| -
|
| - StoreInstanceFieldInstr* store_instance_field = instr->AsStoreInstanceField();
|
| - if (store_instance_field != NULL) {
|
| - return store_instance_field->value()->definition();
|
| - }
|
| -
|
| - StoreStaticFieldInstr* store_static_field = instr->AsStoreStaticField();
|
| - if (store_static_field != NULL) {
|
| - return store_static_field->value()->definition();
|
| - }
|
| -
|
| - UNREACHABLE(); // Should only be called for supported store instructions.
|
| - return NULL;
|
| -}
|
| -
|
| -
|
| -static bool IsPhiDependentPlace(Place* place) {
|
| - return ((place->kind() == Place::kField) ||
|
| - (place->kind() == Place::kVMField)) &&
|
| - (place->instance() != NULL) &&
|
| - place->instance()->IsPhi();
|
| -}
|
| -
|
| -
|
| -// For each place that depends on a phi ensure that equivalent places
|
| -// corresponding to phi input are numbered and record outgoing phi moves
|
| -// for each block which establish correspondence between phi dependent place
|
| -// and phi input's place that is flowing in.
|
| -static PhiPlaceMoves* ComputePhiMoves(
|
| - DirectChainedHashMap<PointerKeyValueTrait<Place> >* map,
|
| - ZoneGrowableArray<Place*>* places) {
|
| - Thread* thread = Thread::Current();
|
| - Zone* zone = thread->zone();
|
| - PhiPlaceMoves* phi_moves = new(zone) PhiPlaceMoves();
|
| -
|
| - for (intptr_t i = 0; i < places->length(); i++) {
|
| - Place* place = (*places)[i];
|
| -
|
| - if (IsPhiDependentPlace(place)) {
|
| - PhiInstr* phi = place->instance()->AsPhi();
|
| - BlockEntryInstr* block = phi->GetBlock();
|
| -
|
| - if (FLAG_trace_optimization) {
|
| - THR_Print("phi dependent place %s\n", place->ToCString());
|
| - }
|
| -
|
| - Place input_place(*place);
|
| - for (intptr_t j = 0; j < phi->InputCount(); j++) {
|
| - input_place.set_instance(phi->InputAt(j)->definition());
|
| -
|
| - Place* result = map->Lookup(&input_place);
|
| - if (result == NULL) {
|
| - result = Place::Wrap(zone, input_place, places->length());
|
| - map->Insert(result);
|
| - places->Add(result);
|
| - if (FLAG_trace_optimization) {
|
| - THR_Print(" adding place %s as %" Pd "\n",
|
| - result->ToCString(),
|
| - result->id());
|
| - }
|
| - }
|
| - phi_moves->CreateOutgoingMove(zone,
|
| - block->PredecessorAt(j),
|
| - result->id(),
|
| - place->id());
|
| - }
|
| - }
|
| - }
|
| -
|
| - return phi_moves;
|
| -}
|
| -
|
| -
|
| -enum CSEMode {
|
| - kOptimizeLoads,
|
| - kOptimizeStores
|
| -};
|
| -
|
| -
|
| -static AliasedSet* NumberPlaces(
|
| - FlowGraph* graph,
|
| - DirectChainedHashMap<PointerKeyValueTrait<Place> >* map,
|
| - CSEMode mode) {
|
| - // Loads representing different expression ids will be collected and
|
| - // used to build per offset kill sets.
|
| - Zone* zone = graph->zone();
|
| - ZoneGrowableArray<Place*>* places =
|
| - new(zone) ZoneGrowableArray<Place*>(10);
|
| -
|
| - bool has_loads = false;
|
| - bool has_stores = false;
|
| - for (BlockIterator it = graph->reverse_postorder_iterator();
|
| - !it.Done();
|
| - it.Advance()) {
|
| - BlockEntryInstr* block = it.Current();
|
| -
|
| - for (ForwardInstructionIterator instr_it(block);
|
| - !instr_it.Done();
|
| - instr_it.Advance()) {
|
| - Instruction* instr = instr_it.Current();
|
| - Place place(instr, &has_loads, &has_stores);
|
| - if (place.kind() == Place::kNone) {
|
| - continue;
|
| - }
|
| -
|
| - Place* result = map->Lookup(&place);
|
| - if (result == NULL) {
|
| - result = Place::Wrap(zone, place, places->length());
|
| - map->Insert(result);
|
| - places->Add(result);
|
| -
|
| - if (FLAG_trace_optimization) {
|
| - THR_Print("numbering %s as %" Pd "\n",
|
| - result->ToCString(),
|
| - result->id());
|
| - }
|
| - }
|
| -
|
| - instr->set_place_id(result->id());
|
| - }
|
| - }
|
| -
|
| - if ((mode == kOptimizeLoads) && !has_loads) {
|
| - return NULL;
|
| - }
|
| - if ((mode == kOptimizeStores) && !has_stores) {
|
| - return NULL;
|
| - }
|
| -
|
| - PhiPlaceMoves* phi_moves = ComputePhiMoves(map, places);
|
| -
|
| - // Build aliasing sets mapping aliases to loads.
|
| - return new(zone) AliasedSet(zone, map, places, phi_moves);
|
| -}
|
| -
|
| -
|
| -class LoadOptimizer : public ValueObject {
|
| - public:
|
| - LoadOptimizer(FlowGraph* graph, AliasedSet* aliased_set)
|
| - : graph_(graph),
|
| - aliased_set_(aliased_set),
|
| - in_(graph_->preorder().length()),
|
| - out_(graph_->preorder().length()),
|
| - gen_(graph_->preorder().length()),
|
| - kill_(graph_->preorder().length()),
|
| - exposed_values_(graph_->preorder().length()),
|
| - out_values_(graph_->preorder().length()),
|
| - phis_(5),
|
| - worklist_(5),
|
| - congruency_worklist_(6),
|
| - in_worklist_(NULL),
|
| - forwarded_(false) {
|
| - const intptr_t num_blocks = graph_->preorder().length();
|
| - for (intptr_t i = 0; i < num_blocks; i++) {
|
| - out_.Add(NULL);
|
| - gen_.Add(new(Z) BitVector(Z, aliased_set_->max_place_id()));
|
| - kill_.Add(new(Z) BitVector(Z, aliased_set_->max_place_id()));
|
| - in_.Add(new(Z) BitVector(Z, aliased_set_->max_place_id()));
|
| -
|
| - exposed_values_.Add(NULL);
|
| - out_values_.Add(NULL);
|
| - }
|
| - }
|
| -
|
| - ~LoadOptimizer() {
|
| - aliased_set_->RollbackAliasedIdentites();
|
| - }
|
| -
|
| - Isolate* isolate() const { return graph_->isolate(); }
|
| - Zone* zone() const { return graph_->zone(); }
|
| -
|
| - static bool OptimizeGraph(FlowGraph* graph) {
|
| - ASSERT(FLAG_load_cse);
|
| - if (FLAG_trace_load_optimization) {
|
| - FlowGraphPrinter::PrintGraph("Before LoadOptimizer", graph);
|
| - }
|
| -
|
| - DirectChainedHashMap<PointerKeyValueTrait<Place> > map;
|
| - AliasedSet* aliased_set = NumberPlaces(graph, &map, kOptimizeLoads);
|
| - if ((aliased_set != NULL) && !aliased_set->IsEmpty()) {
|
| - // If any loads were forwarded return true from Optimize to run load
|
| - // forwarding again. This will allow to forward chains of loads.
|
| - // This is especially important for context variables as they are built
|
| - // as loads from loaded context.
|
| - // TODO(vegorov): renumber newly discovered congruences during the
|
| - // forwarding to forward chains without running whole pass twice.
|
| - LoadOptimizer load_optimizer(graph, aliased_set);
|
| - return load_optimizer.Optimize();
|
| - }
|
| - return false;
|
| - }
|
| -
|
| - private:
|
| - bool Optimize() {
|
| - ComputeInitialSets();
|
| - ComputeOutSets();
|
| - ComputeOutValues();
|
| - if (graph_->is_licm_allowed()) {
|
| - MarkLoopInvariantLoads();
|
| - }
|
| - ForwardLoads();
|
| - EmitPhis();
|
| -
|
| - if (FLAG_trace_load_optimization) {
|
| - FlowGraphPrinter::PrintGraph("After LoadOptimizer", graph_);
|
| - }
|
| -
|
| - return forwarded_;
|
| - }
|
| -
|
| - // Compute sets of loads generated and killed by each block.
|
| - // Additionally compute upwards exposed and generated loads for each block.
|
| - // Exposed loads are those that can be replaced if a corresponding
|
| - // reaching load will be found.
|
| - // Loads that are locally redundant will be replaced as we go through
|
| - // instructions.
|
| - void ComputeInitialSets() {
|
| - for (BlockIterator block_it = graph_->reverse_postorder_iterator();
|
| - !block_it.Done();
|
| - block_it.Advance()) {
|
| - BlockEntryInstr* block = block_it.Current();
|
| - const intptr_t preorder_number = block->preorder_number();
|
| -
|
| - BitVector* kill = kill_[preorder_number];
|
| - BitVector* gen = gen_[preorder_number];
|
| -
|
| - ZoneGrowableArray<Definition*>* exposed_values = NULL;
|
| - ZoneGrowableArray<Definition*>* out_values = NULL;
|
| -
|
| - for (ForwardInstructionIterator instr_it(block);
|
| - !instr_it.Done();
|
| - instr_it.Advance()) {
|
| - Instruction* instr = instr_it.Current();
|
| -
|
| - bool is_load = false, is_store = false;
|
| - Place place(instr, &is_load, &is_store);
|
| -
|
| - BitVector* killed = NULL;
|
| - if (is_store) {
|
| - const intptr_t alias_id =
|
| - aliased_set_->LookupAliasId(place.ToAlias());
|
| - if (alias_id != AliasedSet::kNoAlias) {
|
| - killed = aliased_set_->GetKilledSet(alias_id);
|
| - } else if (!place.IsImmutableField()) {
|
| - // We encountered unknown alias: this means intrablock load
|
| - // forwarding refined parameter of this store, for example
|
| - //
|
| - // o <- alloc()
|
| - // a.f <- o
|
| - // u <- a.f
|
| - // u.x <- null ;; this store alias is *.x
|
| - //
|
| - // after intrablock load forwarding
|
| - //
|
| - // o <- alloc()
|
| - // a.f <- o
|
| - // o.x <- null ;; this store alias is o.x
|
| - //
|
| - // In this case we fallback to using place id recorded in the
|
| - // instruction that still points to the old place with a more
|
| - // generic alias.
|
| - const intptr_t old_alias_id = aliased_set_->LookupAliasId(
|
| - aliased_set_->places()[instr->place_id()]->ToAlias());
|
| - killed = aliased_set_->GetKilledSet(old_alias_id);
|
| - }
|
| -
|
| - if (killed != NULL) {
|
| - kill->AddAll(killed);
|
| - // There is no need to clear out_values when clearing GEN set
|
| - // because only those values that are in the GEN set
|
| - // will ever be used.
|
| - gen->RemoveAll(killed);
|
| - }
|
| -
|
| - // Only forward stores to normal arrays, float64, and simd arrays
|
| - // to loads because other array stores (intXX/uintXX/float32)
|
| - // may implicitly convert the value stored.
|
| - StoreIndexedInstr* array_store = instr->AsStoreIndexed();
|
| - if ((array_store == NULL) ||
|
| - (array_store->class_id() == kArrayCid) ||
|
| - (array_store->class_id() == kTypedDataFloat64ArrayCid) ||
|
| - (array_store->class_id() == kTypedDataFloat32ArrayCid) ||
|
| - (array_store->class_id() == kTypedDataFloat32x4ArrayCid)) {
|
| - Place* canonical_place = aliased_set_->LookupCanonical(&place);
|
| - if (canonical_place != NULL) {
|
| - // Store has a corresponding numbered place that might have a
|
| - // load. Try forwarding stored value to it.
|
| - gen->Add(canonical_place->id());
|
| - if (out_values == NULL) out_values = CreateBlockOutValues();
|
| - (*out_values)[canonical_place->id()] = GetStoredValue(instr);
|
| - }
|
| - }
|
| -
|
| - ASSERT(!instr->IsDefinition() ||
|
| - !IsLoadEliminationCandidate(instr->AsDefinition()));
|
| - continue;
|
| - } else if (is_load) {
|
| - // Check if this load needs renumbering because of the intrablock
|
| - // load forwarding.
|
| - const Place* canonical = aliased_set_->LookupCanonical(&place);
|
| - if ((canonical != NULL) &&
|
| - (canonical->id() != instr->AsDefinition()->place_id())) {
|
| - instr->AsDefinition()->set_place_id(canonical->id());
|
| - }
|
| - }
|
| -
|
| - // If instruction has effects then kill all loads affected.
|
| - if (!instr->Effects().IsNone()) {
|
| - kill->AddAll(aliased_set_->aliased_by_effects());
|
| - // There is no need to clear out_values when removing values from GEN
|
| - // set because only those values that are in the GEN set
|
| - // will ever be used.
|
| - gen->RemoveAll(aliased_set_->aliased_by_effects());
|
| - continue;
|
| - }
|
| -
|
| - Definition* defn = instr->AsDefinition();
|
| - if (defn == NULL) {
|
| - continue;
|
| - }
|
| -
|
| - // For object allocation forward initial values of the fields to
|
| - // subsequent loads. For skip final fields. Final fields are
|
| - // initialized in constructor that potentially can be not inlined into
|
| - // the function that we are currently optimizing. However at the same
|
| - // time we assume that values of the final fields can be forwarded
|
| - // across side-effects. If we add 'null' as known values for these
|
| - // fields here we will incorrectly propagate this null across
|
| - // constructor invocation.
|
| - AllocateObjectInstr* alloc = instr->AsAllocateObject();
|
| - if ((alloc != NULL)) {
|
| - for (Value* use = alloc->input_use_list();
|
| - use != NULL;
|
| - use = use->next_use()) {
|
| - // Look for all immediate loads from this object.
|
| - if (use->use_index() != 0) {
|
| - continue;
|
| - }
|
| -
|
| - LoadFieldInstr* load = use->instruction()->AsLoadField();
|
| - if (load != NULL) {
|
| - // Found a load. Initialize current value of the field to null for
|
| - // normal fields, or with type arguments.
|
| -
|
| - // Forward for all fields for non-escaping objects and only
|
| - // non-final fields and type arguments for escaping ones.
|
| - if (aliased_set_->CanBeAliased(alloc) &&
|
| - (load->field() != NULL) &&
|
| - load->field()->is_final()) {
|
| - continue;
|
| - }
|
| -
|
| - Definition* forward_def = graph_->constant_null();
|
| - if (alloc->ArgumentCount() > 0) {
|
| - ASSERT(alloc->ArgumentCount() == 1);
|
| - intptr_t type_args_offset =
|
| - alloc->cls().type_arguments_field_offset();
|
| - if (load->offset_in_bytes() == type_args_offset) {
|
| - forward_def = alloc->PushArgumentAt(0)->value()->definition();
|
| - }
|
| - }
|
| - gen->Add(load->place_id());
|
| - if (out_values == NULL) out_values = CreateBlockOutValues();
|
| - (*out_values)[load->place_id()] = forward_def;
|
| - }
|
| - }
|
| - continue;
|
| - }
|
| -
|
| - if (!IsLoadEliminationCandidate(defn)) {
|
| - continue;
|
| - }
|
| -
|
| - const intptr_t place_id = defn->place_id();
|
| - if (gen->Contains(place_id)) {
|
| - // This is a locally redundant load.
|
| - ASSERT((out_values != NULL) && ((*out_values)[place_id] != NULL));
|
| -
|
| - Definition* replacement = (*out_values)[place_id];
|
| - EnsureSSATempIndex(graph_, defn, replacement);
|
| - if (FLAG_trace_optimization) {
|
| - THR_Print("Replacing load v%" Pd " with v%" Pd "\n",
|
| - defn->ssa_temp_index(),
|
| - replacement->ssa_temp_index());
|
| - }
|
| -
|
| - defn->ReplaceUsesWith(replacement);
|
| - instr_it.RemoveCurrentFromGraph();
|
| - forwarded_ = true;
|
| - continue;
|
| - } else if (!kill->Contains(place_id)) {
|
| - // This is an exposed load: it is the first representative of a
|
| - // given expression id and it is not killed on the path from
|
| - // the block entry.
|
| - if (exposed_values == NULL) {
|
| - static const intptr_t kMaxExposedValuesInitialSize = 5;
|
| - exposed_values = new(Z) ZoneGrowableArray<Definition*>(
|
| - Utils::Minimum(kMaxExposedValuesInitialSize,
|
| - aliased_set_->max_place_id()));
|
| - }
|
| -
|
| - exposed_values->Add(defn);
|
| - }
|
| -
|
| - gen->Add(place_id);
|
| -
|
| - if (out_values == NULL) out_values = CreateBlockOutValues();
|
| - (*out_values)[place_id] = defn;
|
| - }
|
| -
|
| - exposed_values_[preorder_number] = exposed_values;
|
| - out_values_[preorder_number] = out_values;
|
| - }
|
| - }
|
| -
|
| - static void PerformPhiMoves(PhiPlaceMoves::MovesList phi_moves,
|
| - BitVector* out,
|
| - BitVector* forwarded_loads) {
|
| - forwarded_loads->Clear();
|
| -
|
| - for (intptr_t i = 0; i < phi_moves->length(); i++) {
|
| - const intptr_t from = (*phi_moves)[i].from();
|
| - const intptr_t to = (*phi_moves)[i].to();
|
| - if (from == to) continue;
|
| -
|
| - if (out->Contains(from)) {
|
| - forwarded_loads->Add(to);
|
| - }
|
| - }
|
| -
|
| - for (intptr_t i = 0; i < phi_moves->length(); i++) {
|
| - const intptr_t from = (*phi_moves)[i].from();
|
| - const intptr_t to = (*phi_moves)[i].to();
|
| - if (from == to) continue;
|
| -
|
| - out->Remove(to);
|
| - }
|
| -
|
| - out->AddAll(forwarded_loads);
|
| - }
|
| -
|
| - // Compute OUT sets by propagating them iteratively until fix point
|
| - // is reached.
|
| - void ComputeOutSets() {
|
| - BitVector* temp = new(Z) BitVector(Z, aliased_set_->max_place_id());
|
| - BitVector* forwarded_loads =
|
| - new(Z) BitVector(Z, aliased_set_->max_place_id());
|
| - BitVector* temp_out = new(Z) BitVector(Z, aliased_set_->max_place_id());
|
| -
|
| - bool changed = true;
|
| - while (changed) {
|
| - changed = false;
|
| -
|
| - for (BlockIterator block_it = graph_->reverse_postorder_iterator();
|
| - !block_it.Done();
|
| - block_it.Advance()) {
|
| - BlockEntryInstr* block = block_it.Current();
|
| -
|
| - const intptr_t preorder_number = block->preorder_number();
|
| -
|
| - BitVector* block_in = in_[preorder_number];
|
| - BitVector* block_out = out_[preorder_number];
|
| - BitVector* block_kill = kill_[preorder_number];
|
| - BitVector* block_gen = gen_[preorder_number];
|
| -
|
| - // Compute block_in as the intersection of all out(p) where p
|
| - // is a predecessor of the current block.
|
| - if (block->IsGraphEntry()) {
|
| - temp->Clear();
|
| - } else {
|
| - temp->SetAll();
|
| - ASSERT(block->PredecessorCount() > 0);
|
| - for (intptr_t i = 0; i < block->PredecessorCount(); i++) {
|
| - BlockEntryInstr* pred = block->PredecessorAt(i);
|
| - BitVector* pred_out = out_[pred->preorder_number()];
|
| - if (pred_out == NULL) continue;
|
| - PhiPlaceMoves::MovesList phi_moves =
|
| - aliased_set_->phi_moves()->GetOutgoingMoves(pred);
|
| - if (phi_moves != NULL) {
|
| - // If there are phi moves, perform intersection with
|
| - // a copy of pred_out where the phi moves are applied.
|
| - temp_out->CopyFrom(pred_out);
|
| - PerformPhiMoves(phi_moves, temp_out, forwarded_loads);
|
| - pred_out = temp_out;
|
| - }
|
| - temp->Intersect(pred_out);
|
| - }
|
| - }
|
| -
|
| - if (!temp->Equals(*block_in) || (block_out == NULL)) {
|
| - // If IN set has changed propagate the change to OUT set.
|
| - block_in->CopyFrom(temp);
|
| -
|
| - temp->RemoveAll(block_kill);
|
| - temp->AddAll(block_gen);
|
| -
|
| - if ((block_out == NULL) || !block_out->Equals(*temp)) {
|
| - if (block_out == NULL) {
|
| - block_out = out_[preorder_number] =
|
| - new(Z) BitVector(Z, aliased_set_->max_place_id());
|
| - }
|
| - block_out->CopyFrom(temp);
|
| - changed = true;
|
| - }
|
| - }
|
| - }
|
| - }
|
| - }
|
| -
|
| - // Compute out_values mappings by propagating them in reverse postorder once
|
| - // through the graph. Generate phis on back edges where eager merge is
|
| - // impossible.
|
| - // No replacement is done at this point and thus any out_value[place_id] is
|
| - // changed at most once: from NULL to an actual value.
|
| - // When merging incoming loads we might need to create a phi.
|
| - // These phis are not inserted at the graph immediately because some of them
|
| - // might become redundant after load forwarding is done.
|
| - void ComputeOutValues() {
|
| - GrowableArray<PhiInstr*> pending_phis(5);
|
| - ZoneGrowableArray<Definition*>* temp_forwarded_values = NULL;
|
| -
|
| - for (BlockIterator block_it = graph_->reverse_postorder_iterator();
|
| - !block_it.Done();
|
| - block_it.Advance()) {
|
| - BlockEntryInstr* block = block_it.Current();
|
| -
|
| - const bool can_merge_eagerly = CanMergeEagerly(block);
|
| -
|
| - const intptr_t preorder_number = block->preorder_number();
|
| -
|
| - ZoneGrowableArray<Definition*>* block_out_values =
|
| - out_values_[preorder_number];
|
| -
|
| -
|
| - // If OUT set has changed then we have new values available out of
|
| - // the block. Compute these values creating phi where necessary.
|
| - for (BitVector::Iterator it(out_[preorder_number]);
|
| - !it.Done();
|
| - it.Advance()) {
|
| - const intptr_t place_id = it.Current();
|
| -
|
| - if (block_out_values == NULL) {
|
| - out_values_[preorder_number] = block_out_values =
|
| - CreateBlockOutValues();
|
| - }
|
| -
|
| - if ((*block_out_values)[place_id] == NULL) {
|
| - ASSERT(block->PredecessorCount() > 0);
|
| - Definition* in_value = can_merge_eagerly ?
|
| - MergeIncomingValues(block, place_id) : NULL;
|
| - if ((in_value == NULL) &&
|
| - (in_[preorder_number]->Contains(place_id))) {
|
| - PhiInstr* phi = new(Z) PhiInstr(block->AsJoinEntry(),
|
| - block->PredecessorCount());
|
| - phi->set_place_id(place_id);
|
| - pending_phis.Add(phi);
|
| - in_value = phi;
|
| - }
|
| - (*block_out_values)[place_id] = in_value;
|
| - }
|
| - }
|
| -
|
| - // If the block has outgoing phi moves perform them. Use temporary list
|
| - // of values to ensure that cyclic moves are performed correctly.
|
| - PhiPlaceMoves::MovesList phi_moves =
|
| - aliased_set_->phi_moves()->GetOutgoingMoves(block);
|
| - if ((phi_moves != NULL) && (block_out_values != NULL)) {
|
| - if (temp_forwarded_values == NULL) {
|
| - temp_forwarded_values = CreateBlockOutValues();
|
| - }
|
| -
|
| - for (intptr_t i = 0; i < phi_moves->length(); i++) {
|
| - const intptr_t from = (*phi_moves)[i].from();
|
| - const intptr_t to = (*phi_moves)[i].to();
|
| - if (from == to) continue;
|
| -
|
| - (*temp_forwarded_values)[to] = (*block_out_values)[from];
|
| - }
|
| -
|
| - for (intptr_t i = 0; i < phi_moves->length(); i++) {
|
| - const intptr_t from = (*phi_moves)[i].from();
|
| - const intptr_t to = (*phi_moves)[i].to();
|
| - if (from == to) continue;
|
| -
|
| - (*block_out_values)[to] = (*temp_forwarded_values)[to];
|
| - }
|
| - }
|
| -
|
| - if (FLAG_trace_load_optimization) {
|
| - THR_Print("B%" Pd "\n", block->block_id());
|
| - THR_Print(" IN: ");
|
| - aliased_set_->PrintSet(in_[preorder_number]);
|
| - THR_Print("\n");
|
| -
|
| - THR_Print(" KILL: ");
|
| - aliased_set_->PrintSet(kill_[preorder_number]);
|
| - THR_Print("\n");
|
| -
|
| - THR_Print(" OUT: ");
|
| - aliased_set_->PrintSet(out_[preorder_number]);
|
| - THR_Print("\n");
|
| - }
|
| - }
|
| -
|
| - // All blocks were visited. Fill pending phis with inputs
|
| - // that flow on back edges.
|
| - for (intptr_t i = 0; i < pending_phis.length(); i++) {
|
| - FillPhiInputs(pending_phis[i]);
|
| - }
|
| - }
|
| -
|
| - bool CanMergeEagerly(BlockEntryInstr* block) {
|
| - for (intptr_t i = 0; i < block->PredecessorCount(); i++) {
|
| - BlockEntryInstr* pred = block->PredecessorAt(i);
|
| - if (pred->postorder_number() < block->postorder_number()) {
|
| - return false;
|
| - }
|
| - }
|
| - return true;
|
| - }
|
| -
|
| - void MarkLoopInvariantLoads() {
|
| - const ZoneGrowableArray<BlockEntryInstr*>& loop_headers =
|
| - graph_->LoopHeaders();
|
| -
|
| - ZoneGrowableArray<BitVector*>* invariant_loads =
|
| - new(Z) ZoneGrowableArray<BitVector*>(loop_headers.length());
|
| -
|
| - for (intptr_t i = 0; i < loop_headers.length(); i++) {
|
| - BlockEntryInstr* header = loop_headers[i];
|
| - BlockEntryInstr* pre_header = header->ImmediateDominator();
|
| - if (pre_header == NULL) {
|
| - invariant_loads->Add(NULL);
|
| - continue;
|
| - }
|
| -
|
| - BitVector* loop_gen = new(Z) BitVector(Z, aliased_set_->max_place_id());
|
| - for (BitVector::Iterator loop_it(header->loop_info());
|
| - !loop_it.Done();
|
| - loop_it.Advance()) {
|
| - const intptr_t preorder_number = loop_it.Current();
|
| - loop_gen->AddAll(gen_[preorder_number]);
|
| - }
|
| -
|
| - for (BitVector::Iterator loop_it(header->loop_info());
|
| - !loop_it.Done();
|
| - loop_it.Advance()) {
|
| - const intptr_t preorder_number = loop_it.Current();
|
| - loop_gen->RemoveAll(kill_[preorder_number]);
|
| - }
|
| -
|
| - if (FLAG_trace_optimization) {
|
| - for (BitVector::Iterator it(loop_gen); !it.Done(); it.Advance()) {
|
| - THR_Print("place %s is loop invariant for B%" Pd "\n",
|
| - aliased_set_->places()[it.Current()]->ToCString(),
|
| - header->block_id());
|
| - }
|
| - }
|
| -
|
| - invariant_loads->Add(loop_gen);
|
| - }
|
| -
|
| - graph_->set_loop_invariant_loads(invariant_loads);
|
| - }
|
| -
|
| - // Compute incoming value for the given expression id.
|
| - // Will create a phi if different values are incoming from multiple
|
| - // predecessors.
|
| - Definition* MergeIncomingValues(BlockEntryInstr* block, intptr_t place_id) {
|
| - // First check if the same value is coming in from all predecessors.
|
| - static Definition* const kDifferentValuesMarker =
|
| - reinterpret_cast<Definition*>(-1);
|
| - Definition* incoming = NULL;
|
| - for (intptr_t i = 0; i < block->PredecessorCount(); i++) {
|
| - BlockEntryInstr* pred = block->PredecessorAt(i);
|
| - ZoneGrowableArray<Definition*>* pred_out_values =
|
| - out_values_[pred->preorder_number()];
|
| - if ((pred_out_values == NULL) || ((*pred_out_values)[place_id] == NULL)) {
|
| - return NULL;
|
| - } else if (incoming == NULL) {
|
| - incoming = (*pred_out_values)[place_id];
|
| - } else if (incoming != (*pred_out_values)[place_id]) {
|
| - incoming = kDifferentValuesMarker;
|
| - }
|
| - }
|
| -
|
| - if (incoming != kDifferentValuesMarker) {
|
| - ASSERT(incoming != NULL);
|
| - return incoming;
|
| - }
|
| -
|
| - // Incoming values are different. Phi is required to merge.
|
| - PhiInstr* phi = new(Z) PhiInstr(
|
| - block->AsJoinEntry(), block->PredecessorCount());
|
| - phi->set_place_id(place_id);
|
| - FillPhiInputs(phi);
|
| - return phi;
|
| - }
|
| -
|
| - void FillPhiInputs(PhiInstr* phi) {
|
| - BlockEntryInstr* block = phi->GetBlock();
|
| - const intptr_t place_id = phi->place_id();
|
| -
|
| - for (intptr_t i = 0; i < block->PredecessorCount(); i++) {
|
| - BlockEntryInstr* pred = block->PredecessorAt(i);
|
| - ZoneGrowableArray<Definition*>* pred_out_values =
|
| - out_values_[pred->preorder_number()];
|
| - ASSERT((*pred_out_values)[place_id] != NULL);
|
| -
|
| - // Sets of outgoing values are not linked into use lists so
|
| - // they might contain values that were replaced and removed
|
| - // from the graph by this iteration.
|
| - // To prevent using them we additionally mark definitions themselves
|
| - // as replaced and store a pointer to the replacement.
|
| - Definition* replacement = (*pred_out_values)[place_id]->Replacement();
|
| - Value* input = new(Z) Value(replacement);
|
| - phi->SetInputAt(i, input);
|
| - replacement->AddInputUse(input);
|
| - }
|
| -
|
| - graph_->AllocateSSAIndexes(phi);
|
| - phis_.Add(phi); // Postpone phi insertion until after load forwarding.
|
| -
|
| - if (FLAG_trace_load_optimization) {
|
| - THR_Print("created pending phi %s for %s at B%" Pd "\n",
|
| - phi->ToCString(),
|
| - aliased_set_->places()[place_id]->ToCString(),
|
| - block->block_id());
|
| - }
|
| - }
|
| -
|
| - // Iterate over basic blocks and replace exposed loads with incoming
|
| - // values.
|
| - void ForwardLoads() {
|
| - for (BlockIterator block_it = graph_->reverse_postorder_iterator();
|
| - !block_it.Done();
|
| - block_it.Advance()) {
|
| - BlockEntryInstr* block = block_it.Current();
|
| -
|
| - ZoneGrowableArray<Definition*>* loads =
|
| - exposed_values_[block->preorder_number()];
|
| - if (loads == NULL) continue; // No exposed loads.
|
| -
|
| - BitVector* in = in_[block->preorder_number()];
|
| -
|
| - for (intptr_t i = 0; i < loads->length(); i++) {
|
| - Definition* load = (*loads)[i];
|
| - if (!in->Contains(load->place_id())) continue; // No incoming value.
|
| -
|
| - Definition* replacement = MergeIncomingValues(block, load->place_id());
|
| - ASSERT(replacement != NULL);
|
| -
|
| - // Sets of outgoing values are not linked into use lists so
|
| - // they might contain values that were replace and removed
|
| - // from the graph by this iteration.
|
| - // To prevent using them we additionally mark definitions themselves
|
| - // as replaced and store a pointer to the replacement.
|
| - replacement = replacement->Replacement();
|
| -
|
| - if (load != replacement) {
|
| - EnsureSSATempIndex(graph_, load, replacement);
|
| -
|
| - if (FLAG_trace_optimization) {
|
| - THR_Print("Replacing load v%" Pd " with v%" Pd "\n",
|
| - load->ssa_temp_index(),
|
| - replacement->ssa_temp_index());
|
| - }
|
| -
|
| - load->ReplaceUsesWith(replacement);
|
| - load->RemoveFromGraph();
|
| - load->SetReplacement(replacement);
|
| - forwarded_ = true;
|
| - }
|
| - }
|
| - }
|
| - }
|
| -
|
| - // Check if the given phi take the same value on all code paths.
|
| - // Eliminate it as redundant if this is the case.
|
| - // When analyzing phi operands assumes that only generated during
|
| - // this load phase can be redundant. They can be distinguished because
|
| - // they are not marked alive.
|
| - // TODO(vegorov): move this into a separate phase over all phis.
|
| - bool EliminateRedundantPhi(PhiInstr* phi) {
|
| - Definition* value = NULL; // Possible value of this phi.
|
| -
|
| - worklist_.Clear();
|
| - if (in_worklist_ == NULL) {
|
| - in_worklist_ = new(Z) BitVector(Z, graph_->current_ssa_temp_index());
|
| - } else {
|
| - in_worklist_->Clear();
|
| - }
|
| -
|
| - worklist_.Add(phi);
|
| - in_worklist_->Add(phi->ssa_temp_index());
|
| -
|
| - for (intptr_t i = 0; i < worklist_.length(); i++) {
|
| - PhiInstr* phi = worklist_[i];
|
| -
|
| - for (intptr_t i = 0; i < phi->InputCount(); i++) {
|
| - Definition* input = phi->InputAt(i)->definition();
|
| - if (input == phi) continue;
|
| -
|
| - PhiInstr* phi_input = input->AsPhi();
|
| - if ((phi_input != NULL) && !phi_input->is_alive()) {
|
| - if (!in_worklist_->Contains(phi_input->ssa_temp_index())) {
|
| - worklist_.Add(phi_input);
|
| - in_worklist_->Add(phi_input->ssa_temp_index());
|
| - }
|
| - continue;
|
| - }
|
| -
|
| - if (value == NULL) {
|
| - value = input;
|
| - } else if (value != input) {
|
| - return false; // This phi is not redundant.
|
| - }
|
| - }
|
| - }
|
| -
|
| - // All phis in the worklist are redundant and have the same computed
|
| - // value on all code paths.
|
| - ASSERT(value != NULL);
|
| - for (intptr_t i = 0; i < worklist_.length(); i++) {
|
| - worklist_[i]->ReplaceUsesWith(value);
|
| - }
|
| -
|
| - return true;
|
| - }
|
| -
|
| - // Returns true if definitions are congruent assuming their inputs
|
| - // are congruent.
|
| - bool CanBeCongruent(Definition* a, Definition* b) {
|
| - return (a->tag() == b->tag()) &&
|
| - ((a->IsPhi() && (a->GetBlock() == b->GetBlock())) ||
|
| - (a->AllowsCSE() && a->Dependencies().IsNone() &&
|
| - a->AttributesEqual(b)));
|
| - }
|
| -
|
| - // Given two definitions check if they are congruent under assumption that
|
| - // their inputs will be proven congruent. If they are - add them to the
|
| - // worklist to check their inputs' congruency.
|
| - // Returns true if pair was added to the worklist or is already in the
|
| - // worklist and false if a and b are not congruent.
|
| - bool AddPairToCongruencyWorklist(Definition* a, Definition* b) {
|
| - if (!CanBeCongruent(a, b)) {
|
| - return false;
|
| - }
|
| -
|
| - // If a is already in the worklist check if it is being compared to b.
|
| - // Give up if it is not.
|
| - if (in_worklist_->Contains(a->ssa_temp_index())) {
|
| - for (intptr_t i = 0; i < congruency_worklist_.length(); i += 2) {
|
| - if (a == congruency_worklist_[i]) {
|
| - return (b == congruency_worklist_[i + 1]);
|
| - }
|
| - }
|
| - UNREACHABLE();
|
| - } else if (in_worklist_->Contains(b->ssa_temp_index())) {
|
| - return AddPairToCongruencyWorklist(b, a);
|
| - }
|
| -
|
| - congruency_worklist_.Add(a);
|
| - congruency_worklist_.Add(b);
|
| - in_worklist_->Add(a->ssa_temp_index());
|
| - return true;
|
| - }
|
| -
|
| - bool AreInputsCongruent(Definition* a, Definition* b) {
|
| - ASSERT(a->tag() == b->tag());
|
| - ASSERT(a->InputCount() == b->InputCount());
|
| - for (intptr_t j = 0; j < a->InputCount(); j++) {
|
| - Definition* inputA = a->InputAt(j)->definition();
|
| - Definition* inputB = b->InputAt(j)->definition();
|
| -
|
| - if (inputA != inputB) {
|
| - if (!AddPairToCongruencyWorklist(inputA, inputB)) {
|
| - return false;
|
| - }
|
| - }
|
| - }
|
| - return true;
|
| - }
|
| -
|
| - // Returns true if instruction dom dominates instruction other.
|
| - static bool Dominates(Instruction* dom, Instruction* other) {
|
| - BlockEntryInstr* dom_block = dom->GetBlock();
|
| - BlockEntryInstr* other_block = other->GetBlock();
|
| -
|
| - if (dom_block == other_block) {
|
| - for (Instruction* current = dom->next();
|
| - current != NULL;
|
| - current = current->next()) {
|
| - if (current == other) {
|
| - return true;
|
| - }
|
| - }
|
| - return false;
|
| - }
|
| -
|
| - return dom_block->Dominates(other_block);
|
| - }
|
| -
|
| - // Replace the given phi with another if they are congruent.
|
| - // Returns true if succeeds.
|
| - bool ReplacePhiWith(PhiInstr* phi, PhiInstr* replacement) {
|
| - ASSERT(phi->InputCount() == replacement->InputCount());
|
| - ASSERT(phi->block() == replacement->block());
|
| -
|
| - congruency_worklist_.Clear();
|
| - if (in_worklist_ == NULL) {
|
| - in_worklist_ = new(Z) BitVector(Z, graph_->current_ssa_temp_index());
|
| - } else {
|
| - in_worklist_->Clear();
|
| - }
|
| -
|
| - // During the comparison worklist contains pairs of definitions to be
|
| - // compared.
|
| - if (!AddPairToCongruencyWorklist(phi, replacement)) {
|
| - return false;
|
| - }
|
| -
|
| - // Process the worklist. It might grow during each comparison step.
|
| - for (intptr_t i = 0; i < congruency_worklist_.length(); i += 2) {
|
| - if (!AreInputsCongruent(congruency_worklist_[i],
|
| - congruency_worklist_[i + 1])) {
|
| - return false;
|
| - }
|
| - }
|
| -
|
| - // At this point worklist contains pairs of congruent definitions.
|
| - // Replace the one member of the pair with another maintaining proper
|
| - // domination relation between definitions and uses.
|
| - for (intptr_t i = 0; i < congruency_worklist_.length(); i += 2) {
|
| - Definition* a = congruency_worklist_[i];
|
| - Definition* b = congruency_worklist_[i + 1];
|
| -
|
| - // If these definitions are not phis then we need to pick up one
|
| - // that dominates another as the replacement: if a dominates b swap them.
|
| - // Note: both a and b are used as a phi input at the same block B which
|
| - // means a dominates B and b dominates B, which guarantees that either
|
| - // a dominates b or b dominates a.
|
| - if (!a->IsPhi()) {
|
| - if (Dominates(a, b)) {
|
| - Definition* t = a;
|
| - a = b;
|
| - b = t;
|
| - }
|
| - ASSERT(Dominates(b, a));
|
| - }
|
| -
|
| - if (FLAG_trace_load_optimization) {
|
| - THR_Print("Replacing %s with congruent %s\n",
|
| - a->ToCString(),
|
| - b->ToCString());
|
| - }
|
| -
|
| - a->ReplaceUsesWith(b);
|
| - if (a->IsPhi()) {
|
| - // We might be replacing a phi introduced by the load forwarding
|
| - // that is not inserted in the graph yet.
|
| - ASSERT(b->IsPhi());
|
| - PhiInstr* phi_a = a->AsPhi();
|
| - if (phi_a->is_alive()) {
|
| - phi_a->mark_dead();
|
| - phi_a->block()->RemovePhi(phi_a);
|
| - phi_a->UnuseAllInputs();
|
| - }
|
| - } else {
|
| - a->RemoveFromGraph();
|
| - }
|
| - }
|
| -
|
| - return true;
|
| - }
|
| -
|
| - // Insert the given phi into the graph. Attempt to find an equal one in the
|
| - // target block first.
|
| - // Returns true if the phi was inserted and false if it was replaced.
|
| - bool EmitPhi(PhiInstr* phi) {
|
| - for (PhiIterator it(phi->block()); !it.Done(); it.Advance()) {
|
| - if (ReplacePhiWith(phi, it.Current())) {
|
| - return false;
|
| - }
|
| - }
|
| -
|
| - phi->mark_alive();
|
| - phi->block()->InsertPhi(phi);
|
| - return true;
|
| - }
|
| -
|
| - // Phis have not yet been inserted into the graph but they have uses of
|
| - // their inputs. Insert the non-redundant ones and clear the input uses
|
| - // of the redundant ones.
|
| - void EmitPhis() {
|
| - // First eliminate all redundant phis.
|
| - for (intptr_t i = 0; i < phis_.length(); i++) {
|
| - PhiInstr* phi = phis_[i];
|
| - if (!phi->HasUses() || EliminateRedundantPhi(phi)) {
|
| - phi->UnuseAllInputs();
|
| - phis_[i] = NULL;
|
| - }
|
| - }
|
| -
|
| - // Now emit phis or replace them with equal phis already present in the
|
| - // graph.
|
| - for (intptr_t i = 0; i < phis_.length(); i++) {
|
| - PhiInstr* phi = phis_[i];
|
| - if ((phi != NULL) && (!phi->HasUses() || !EmitPhi(phi))) {
|
| - phi->UnuseAllInputs();
|
| - }
|
| - }
|
| - }
|
| -
|
| - ZoneGrowableArray<Definition*>* CreateBlockOutValues() {
|
| - ZoneGrowableArray<Definition*>* out =
|
| - new(Z) ZoneGrowableArray<Definition*>(aliased_set_->max_place_id());
|
| - for (intptr_t i = 0; i < aliased_set_->max_place_id(); i++) {
|
| - out->Add(NULL);
|
| - }
|
| - return out;
|
| - }
|
| -
|
| - FlowGraph* graph_;
|
| - DirectChainedHashMap<PointerKeyValueTrait<Place> >* map_;
|
| -
|
| - // Mapping between field offsets in words and expression ids of loads from
|
| - // that offset.
|
| - AliasedSet* aliased_set_;
|
| -
|
| - // Per block sets of expression ids for loads that are: incoming (available
|
| - // on the entry), outgoing (available on the exit), generated and killed.
|
| - GrowableArray<BitVector*> in_;
|
| - GrowableArray<BitVector*> out_;
|
| - GrowableArray<BitVector*> gen_;
|
| - GrowableArray<BitVector*> kill_;
|
| -
|
| - // Per block list of upwards exposed loads.
|
| - GrowableArray<ZoneGrowableArray<Definition*>*> exposed_values_;
|
| -
|
| - // Per block mappings between expression ids and outgoing definitions that
|
| - // represent those ids.
|
| - GrowableArray<ZoneGrowableArray<Definition*>*> out_values_;
|
| -
|
| - // List of phis generated during ComputeOutValues and ForwardLoads.
|
| - // Some of these phis might be redundant and thus a separate pass is
|
| - // needed to emit only non-redundant ones.
|
| - GrowableArray<PhiInstr*> phis_;
|
| -
|
| - // Auxiliary worklist used by redundant phi elimination.
|
| - GrowableArray<PhiInstr*> worklist_;
|
| - GrowableArray<Definition*> congruency_worklist_;
|
| - BitVector* in_worklist_;
|
| -
|
| -
|
| - // True if any load was eliminated.
|
| - bool forwarded_;
|
| -
|
| - DISALLOW_COPY_AND_ASSIGN(LoadOptimizer);
|
| -};
|
| -
|
| -
|
| -class StoreOptimizer : public LivenessAnalysis {
|
| - public:
|
| - StoreOptimizer(FlowGraph* graph,
|
| - AliasedSet* aliased_set,
|
| - DirectChainedHashMap<PointerKeyValueTrait<Place> >* map)
|
| - : LivenessAnalysis(aliased_set->max_place_id(), graph->postorder()),
|
| - graph_(graph),
|
| - map_(map),
|
| - aliased_set_(aliased_set),
|
| - exposed_stores_(graph_->postorder().length()) {
|
| - const intptr_t num_blocks = graph_->postorder().length();
|
| - for (intptr_t i = 0; i < num_blocks; i++) {
|
| - exposed_stores_.Add(NULL);
|
| - }
|
| - }
|
| -
|
| - static void OptimizeGraph(FlowGraph* graph) {
|
| - ASSERT(FLAG_load_cse);
|
| - if (FLAG_trace_load_optimization) {
|
| - FlowGraphPrinter::PrintGraph("Before StoreOptimizer", graph);
|
| - }
|
| -
|
| - DirectChainedHashMap<PointerKeyValueTrait<Place> > map;
|
| - AliasedSet* aliased_set = NumberPlaces(graph, &map, kOptimizeStores);
|
| - if ((aliased_set != NULL) && !aliased_set->IsEmpty()) {
|
| - StoreOptimizer store_optimizer(graph, aliased_set, &map);
|
| - store_optimizer.Optimize();
|
| - }
|
| - }
|
| -
|
| - private:
|
| - void Optimize() {
|
| - Analyze();
|
| - if (FLAG_trace_load_optimization) {
|
| - Dump();
|
| - }
|
| - EliminateDeadStores();
|
| - if (FLAG_trace_load_optimization) {
|
| - FlowGraphPrinter::PrintGraph("After StoreOptimizer", graph_);
|
| - }
|
| - }
|
| -
|
| - bool CanEliminateStore(Instruction* instr) {
|
| - switch (instr->tag()) {
|
| - case Instruction::kStoreInstanceField: {
|
| - StoreInstanceFieldInstr* store_instance = instr->AsStoreInstanceField();
|
| - // Can't eliminate stores that initialize fields.
|
| - return !(store_instance->is_potential_unboxed_initialization() ||
|
| - store_instance->is_object_reference_initialization());
|
| - }
|
| - case Instruction::kStoreIndexed:
|
| - case Instruction::kStoreStaticField:
|
| - return true;
|
| - default:
|
| - UNREACHABLE();
|
| - return false;
|
| - }
|
| - }
|
| -
|
| - virtual void ComputeInitialSets() {
|
| - Zone* zone = graph_->zone();
|
| - BitVector* all_places = new(zone) BitVector(zone,
|
| - aliased_set_->max_place_id());
|
| - all_places->SetAll();
|
| - for (BlockIterator block_it = graph_->postorder_iterator();
|
| - !block_it.Done();
|
| - block_it.Advance()) {
|
| - BlockEntryInstr* block = block_it.Current();
|
| - const intptr_t postorder_number = block->postorder_number();
|
| -
|
| - BitVector* kill = kill_[postorder_number];
|
| - BitVector* live_in = live_in_[postorder_number];
|
| - BitVector* live_out = live_out_[postorder_number];
|
| -
|
| - ZoneGrowableArray<Instruction*>* exposed_stores = NULL;
|
| -
|
| - // Iterate backwards starting at the last instruction.
|
| - for (BackwardInstructionIterator instr_it(block);
|
| - !instr_it.Done();
|
| - instr_it.Advance()) {
|
| - Instruction* instr = instr_it.Current();
|
| -
|
| - bool is_load = false;
|
| - bool is_store = false;
|
| - Place place(instr, &is_load, &is_store);
|
| - if (place.IsImmutableField()) {
|
| - // Loads/stores of final fields do not participate.
|
| - continue;
|
| - }
|
| -
|
| - // Handle stores.
|
| - if (is_store) {
|
| - if (kill->Contains(instr->place_id())) {
|
| - if (!live_in->Contains(instr->place_id()) &&
|
| - CanEliminateStore(instr)) {
|
| - if (FLAG_trace_optimization) {
|
| - THR_Print(
|
| - "Removing dead store to place %" Pd " in block B%" Pd "\n",
|
| - instr->place_id(), block->block_id());
|
| - }
|
| - instr_it.RemoveCurrentFromGraph();
|
| - }
|
| - } else if (!live_in->Contains(instr->place_id())) {
|
| - // Mark this store as down-ward exposed: They are the only
|
| - // candidates for the global store elimination.
|
| - if (exposed_stores == NULL) {
|
| - const intptr_t kMaxExposedStoresInitialSize = 5;
|
| - exposed_stores = new(zone) ZoneGrowableArray<Instruction*>(
|
| - Utils::Minimum(kMaxExposedStoresInitialSize,
|
| - aliased_set_->max_place_id()));
|
| - }
|
| - exposed_stores->Add(instr);
|
| - }
|
| - // Interfering stores kill only loads from the same place.
|
| - kill->Add(instr->place_id());
|
| - live_in->Remove(instr->place_id());
|
| - continue;
|
| - }
|
| -
|
| - // Handle side effects, deoptimization and function return.
|
| - if (!instr->Effects().IsNone() ||
|
| - instr->CanDeoptimize() ||
|
| - instr->IsThrow() ||
|
| - instr->IsReThrow() ||
|
| - instr->IsReturn()) {
|
| - // Instructions that return from the function, instructions with side
|
| - // effects and instructions that can deoptimize are considered as
|
| - // loads from all places.
|
| - live_in->CopyFrom(all_places);
|
| - if (instr->IsThrow() || instr->IsReThrow() || instr->IsReturn()) {
|
| - // Initialize live-out for exit blocks since it won't be computed
|
| - // otherwise during the fixed point iteration.
|
| - live_out->CopyFrom(all_places);
|
| - }
|
| - continue;
|
| - }
|
| -
|
| - // Handle loads.
|
| - Definition* defn = instr->AsDefinition();
|
| - if ((defn != NULL) && IsLoadEliminationCandidate(defn)) {
|
| - const intptr_t alias = aliased_set_->LookupAliasId(place.ToAlias());
|
| - live_in->AddAll(aliased_set_->GetKilledSet(alias));
|
| - continue;
|
| - }
|
| - }
|
| - exposed_stores_[postorder_number] = exposed_stores;
|
| - }
|
| - if (FLAG_trace_load_optimization) {
|
| - Dump();
|
| - THR_Print("---\n");
|
| - }
|
| - }
|
| -
|
| - void EliminateDeadStores() {
|
| - // Iteration order does not matter here.
|
| - for (BlockIterator block_it = graph_->postorder_iterator();
|
| - !block_it.Done();
|
| - block_it.Advance()) {
|
| - BlockEntryInstr* block = block_it.Current();
|
| - const intptr_t postorder_number = block->postorder_number();
|
| -
|
| - BitVector* live_out = live_out_[postorder_number];
|
| -
|
| - ZoneGrowableArray<Instruction*>* exposed_stores =
|
| - exposed_stores_[postorder_number];
|
| - if (exposed_stores == NULL) continue; // No exposed stores.
|
| -
|
| - // Iterate over candidate stores.
|
| - for (intptr_t i = 0; i < exposed_stores->length(); ++i) {
|
| - Instruction* instr = (*exposed_stores)[i];
|
| - bool is_load = false;
|
| - bool is_store = false;
|
| - Place place(instr, &is_load, &is_store);
|
| - ASSERT(!is_load && is_store);
|
| - if (place.IsImmutableField()) {
|
| - // Final field do not participate in dead store elimination.
|
| - continue;
|
| - }
|
| - // Eliminate a downward exposed store if the corresponding place is not
|
| - // in live-out.
|
| - if (!live_out->Contains(instr->place_id()) &&
|
| - CanEliminateStore(instr)) {
|
| - if (FLAG_trace_optimization) {
|
| - THR_Print("Removing dead store to place %" Pd " block B%" Pd "\n",
|
| - instr->place_id(), block->block_id());
|
| - }
|
| - instr->RemoveFromGraph(/* ignored */ false);
|
| - }
|
| - }
|
| - }
|
| - }
|
| -
|
| - FlowGraph* graph_;
|
| - DirectChainedHashMap<PointerKeyValueTrait<Place> >* map_;
|
| -
|
| - // Mapping between field offsets in words and expression ids of loads from
|
| - // that offset.
|
| - AliasedSet* aliased_set_;
|
| -
|
| - // Per block list of downward exposed stores.
|
| - GrowableArray<ZoneGrowableArray<Instruction*>*> exposed_stores_;
|
| -
|
| - DISALLOW_COPY_AND_ASSIGN(StoreOptimizer);
|
| -};
|
| -
|
| -
|
| -void DeadStoreElimination::Optimize(FlowGraph* graph) {
|
| - if (FLAG_dead_store_elimination) {
|
| - StoreOptimizer::OptimizeGraph(graph);
|
| - }
|
| -}
|
| -
|
| -
|
| -// Returns true iff this definition is used in a non-phi instruction.
|
| -static bool HasRealUse(Definition* def) {
|
| - // Environment uses are real (non-phi) uses.
|
| - if (def->env_use_list() != NULL) return true;
|
| -
|
| - for (Value::Iterator it(def->input_use_list());
|
| - !it.Done();
|
| - it.Advance()) {
|
| - if (!it.Current()->instruction()->IsPhi()) return true;
|
| - }
|
| - return false;
|
| -}
|
| -
|
| -
|
| -void DeadCodeElimination::EliminateDeadPhis(FlowGraph* flow_graph) {
|
| - GrowableArray<PhiInstr*> live_phis;
|
| - for (BlockIterator b = flow_graph->postorder_iterator();
|
| - !b.Done();
|
| - b.Advance()) {
|
| - JoinEntryInstr* join = b.Current()->AsJoinEntry();
|
| - if (join != NULL) {
|
| - for (PhiIterator it(join); !it.Done(); it.Advance()) {
|
| - PhiInstr* phi = it.Current();
|
| - // Phis that have uses and phis inside try blocks are
|
| - // marked as live.
|
| - if (HasRealUse(phi) || join->InsideTryBlock()) {
|
| - live_phis.Add(phi);
|
| - phi->mark_alive();
|
| - } else {
|
| - phi->mark_dead();
|
| - }
|
| - }
|
| - }
|
| - }
|
| -
|
| - while (!live_phis.is_empty()) {
|
| - PhiInstr* phi = live_phis.RemoveLast();
|
| - for (intptr_t i = 0; i < phi->InputCount(); i++) {
|
| - Value* val = phi->InputAt(i);
|
| - PhiInstr* used_phi = val->definition()->AsPhi();
|
| - if ((used_phi != NULL) && !used_phi->is_alive()) {
|
| - used_phi->mark_alive();
|
| - live_phis.Add(used_phi);
|
| - }
|
| - }
|
| - }
|
| -
|
| - for (BlockIterator it(flow_graph->postorder_iterator());
|
| - !it.Done();
|
| - it.Advance()) {
|
| - JoinEntryInstr* join = it.Current()->AsJoinEntry();
|
| - if (join != NULL) {
|
| - if (join->phis_ == NULL) continue;
|
| -
|
| - // Eliminate dead phis and compact the phis_ array of the block.
|
| - intptr_t to_index = 0;
|
| - for (intptr_t i = 0; i < join->phis_->length(); ++i) {
|
| - PhiInstr* phi = (*join->phis_)[i];
|
| - if (phi != NULL) {
|
| - if (!phi->is_alive()) {
|
| - phi->ReplaceUsesWith(flow_graph->constant_null());
|
| - phi->UnuseAllInputs();
|
| - (*join->phis_)[i] = NULL;
|
| - if (FLAG_trace_optimization) {
|
| - THR_Print("Removing dead phi v%" Pd "\n", phi->ssa_temp_index());
|
| - }
|
| - } else if (phi->IsRedundant()) {
|
| - phi->ReplaceUsesWith(phi->InputAt(0)->definition());
|
| - phi->UnuseAllInputs();
|
| - (*join->phis_)[i] = NULL;
|
| - if (FLAG_trace_optimization) {
|
| - THR_Print("Removing redundant phi v%" Pd "\n",
|
| - phi->ssa_temp_index());
|
| - }
|
| - } else {
|
| - (*join->phis_)[to_index++] = phi;
|
| - }
|
| - }
|
| - }
|
| - if (to_index == 0) {
|
| - join->phis_ = NULL;
|
| - } else {
|
| - join->phis_->TruncateTo(to_index);
|
| - }
|
| - }
|
| - }
|
| -}
|
| -
|
| -
|
| -class CSEInstructionMap : public ValueObject {
|
| - public:
|
| - // Right now CSE and LICM track a single effect: possible externalization of
|
| - // strings.
|
| - // Other effects like modifications of fields are tracked in a separate load
|
| - // forwarding pass via Alias structure.
|
| - COMPILE_ASSERT(EffectSet::kLastEffect == 1);
|
| -
|
| - CSEInstructionMap() : independent_(), dependent_() { }
|
| - explicit CSEInstructionMap(const CSEInstructionMap& other)
|
| - : ValueObject(),
|
| - independent_(other.independent_),
|
| - dependent_(other.dependent_) {
|
| - }
|
| -
|
| - void RemoveAffected(EffectSet effects) {
|
| - if (!effects.IsNone()) {
|
| - dependent_.Clear();
|
| - }
|
| - }
|
| -
|
| - Instruction* Lookup(Instruction* other) const {
|
| - return GetMapFor(other)->Lookup(other);
|
| - }
|
| -
|
| - void Insert(Instruction* instr) {
|
| - return GetMapFor(instr)->Insert(instr);
|
| - }
|
| -
|
| - private:
|
| - typedef DirectChainedHashMap<PointerKeyValueTrait<Instruction> > Map;
|
| -
|
| - Map* GetMapFor(Instruction* instr) {
|
| - return instr->Dependencies().IsNone() ? &independent_ : &dependent_;
|
| - }
|
| -
|
| - const Map* GetMapFor(Instruction* instr) const {
|
| - return instr->Dependencies().IsNone() ? &independent_ : &dependent_;
|
| - }
|
| -
|
| - // All computations that are not affected by any side-effect.
|
| - // Majority of computations are not affected by anything and will be in
|
| - // this map.
|
| - Map independent_;
|
| -
|
| - // All computations that are affected by side effect.
|
| - Map dependent_;
|
| -};
|
| -
|
| -
|
| -bool DominatorBasedCSE::Optimize(FlowGraph* graph) {
|
| - bool changed = false;
|
| - if (FLAG_load_cse) {
|
| - changed = LoadOptimizer::OptimizeGraph(graph) || changed;
|
| - }
|
| -
|
| - CSEInstructionMap map;
|
| - changed = OptimizeRecursive(graph, graph->graph_entry(), &map) || changed;
|
| -
|
| - return changed;
|
| -}
|
| -
|
| -
|
| -bool DominatorBasedCSE::OptimizeRecursive(
|
| - FlowGraph* graph,
|
| - BlockEntryInstr* block,
|
| - CSEInstructionMap* map) {
|
| - bool changed = false;
|
| - for (ForwardInstructionIterator it(block); !it.Done(); it.Advance()) {
|
| - Instruction* current = it.Current();
|
| - if (current->AllowsCSE()) {
|
| - Instruction* replacement = map->Lookup(current);
|
| - if ((replacement != NULL) &&
|
| - graph->block_effects()->IsAvailableAt(replacement, block)) {
|
| - // Replace current with lookup result.
|
| - ReplaceCurrentInstruction(&it, current, replacement, graph);
|
| - changed = true;
|
| - continue;
|
| - }
|
| -
|
| - // For simplicity we assume that instruction either does not depend on
|
| - // anything or does not affect anything. If this is not the case then
|
| - // we should first remove affected instructions from the map and
|
| - // then add instruction to the map so that it does not kill itself.
|
| - ASSERT(current->Effects().IsNone() || current->Dependencies().IsNone());
|
| - map->Insert(current);
|
| - }
|
| -
|
| - map->RemoveAffected(current->Effects());
|
| - }
|
| -
|
| - // Process children in the dominator tree recursively.
|
| - intptr_t num_children = block->dominated_blocks().length();
|
| - for (intptr_t i = 0; i < num_children; ++i) {
|
| - BlockEntryInstr* child = block->dominated_blocks()[i];
|
| - if (i < num_children - 1) {
|
| - // Copy map.
|
| - CSEInstructionMap child_map(*map);
|
| - changed = OptimizeRecursive(graph, child, &child_map) || changed;
|
| - } else {
|
| - // Reuse map for the last child.
|
| - changed = OptimizeRecursive(graph, child, map) || changed;
|
| - }
|
| - }
|
| - return changed;
|
| -}
|
| -
|
| -
|
| -void FlowGraphOptimizer::EliminateEnvironments() {
|
| - // After this pass we can no longer perform LICM and hoist instructions
|
| - // that can deoptimize.
|
| -
|
| - flow_graph_->disallow_licm();
|
| - for (intptr_t i = 0; i < block_order_.length(); ++i) {
|
| - BlockEntryInstr* block = block_order_[i];
|
| - block->RemoveEnvironment();
|
| - for (ForwardInstructionIterator it(block); !it.Done(); it.Advance()) {
|
| - Instruction* current = it.Current();
|
| - if (!current->CanDeoptimize()) {
|
| - // TODO(srdjan): --source-lines needs deopt environments to get at
|
| - // the code for this instruction, however, leaving the environment
|
| - // changes code.
|
| - current->RemoveEnvironment();
|
| - }
|
| - }
|
| - }
|
| -}
|
| -
|
| -
|
| -enum SafeUseCheck { kOptimisticCheck, kStrictCheck };
|
| -
|
| -// Check if the use is safe for allocation sinking. Allocation sinking
|
| -// candidates can only be used at store instructions:
|
| -//
|
| -// - any store into the allocation candidate itself is unconditionally safe
|
| -// as it just changes the rematerialization state of this candidate;
|
| -// - store into another object is only safe if another object is allocation
|
| -// candidate.
|
| -//
|
| -// We use a simple fix-point algorithm to discover the set of valid candidates
|
| -// (see CollectCandidates method), that's why this IsSafeUse can operate in two
|
| -// modes:
|
| -//
|
| -// - optimistic, when every allocation is assumed to be an allocation
|
| -// sinking candidate;
|
| -// - strict, when only marked allocations are assumed to be allocation
|
| -// sinking candidates.
|
| -//
|
| -// Fix-point algorithm in CollectCandiates first collects a set of allocations
|
| -// optimistically and then checks each collected candidate strictly and unmarks
|
| -// invalid candidates transitively until only strictly valid ones remain.
|
| -static bool IsSafeUse(Value* use, SafeUseCheck check_type) {
|
| - if (use->instruction()->IsMaterializeObject()) {
|
| - return true;
|
| - }
|
| -
|
| - StoreInstanceFieldInstr* store = use->instruction()->AsStoreInstanceField();
|
| - if (store != NULL) {
|
| - if (use == store->value()) {
|
| - Definition* instance = store->instance()->definition();
|
| - return instance->IsAllocateObject() &&
|
| - ((check_type == kOptimisticCheck) ||
|
| - instance->Identity().IsAllocationSinkingCandidate());
|
| - }
|
| - return true;
|
| - }
|
| -
|
| - return false;
|
| -}
|
| -
|
| -
|
| -// Right now we are attempting to sink allocation only into
|
| -// deoptimization exit. So candidate should only be used in StoreInstanceField
|
| -// instructions that write into fields of the allocated object.
|
| -// We do not support materialization of the object that has type arguments.
|
| -static bool IsAllocationSinkingCandidate(Definition* alloc,
|
| - SafeUseCheck check_type) {
|
| - for (Value* use = alloc->input_use_list();
|
| - use != NULL;
|
| - use = use->next_use()) {
|
| - if (!IsSafeUse(use, check_type)) {
|
| - if (FLAG_trace_optimization) {
|
| - THR_Print("use of %s at %s is unsafe for allocation sinking\n",
|
| - alloc->ToCString(),
|
| - use->instruction()->ToCString());
|
| - }
|
| - return false;
|
| - }
|
| - }
|
| -
|
| - return true;
|
| -}
|
| -
|
| -
|
| -// If the given use is a store into an object then return an object we are
|
| -// storing into.
|
| -static Definition* StoreInto(Value* use) {
|
| - StoreInstanceFieldInstr* store = use->instruction()->AsStoreInstanceField();
|
| - if (store != NULL) {
|
| - return store->instance()->definition();
|
| - }
|
| -
|
| - return NULL;
|
| -}
|
| -
|
| -
|
| -// Remove the given allocation from the graph. It is not observable.
|
| -// If deoptimization occurs the object will be materialized.
|
| -void AllocationSinking::EliminateAllocation(Definition* alloc) {
|
| - ASSERT(IsAllocationSinkingCandidate(alloc, kStrictCheck));
|
| -
|
| - if (FLAG_trace_optimization) {
|
| - THR_Print("removing allocation from the graph: v%" Pd "\n",
|
| - alloc->ssa_temp_index());
|
| - }
|
| -
|
| - // As an allocation sinking candidate it is only used in stores to its own
|
| - // fields. Remove these stores.
|
| - for (Value* use = alloc->input_use_list();
|
| - use != NULL;
|
| - use = alloc->input_use_list()) {
|
| - use->instruction()->RemoveFromGraph();
|
| - }
|
| -
|
| - // There should be no environment uses. The pass replaced them with
|
| - // MaterializeObject instructions.
|
| -#ifdef DEBUG
|
| - for (Value* use = alloc->env_use_list();
|
| - use != NULL;
|
| - use = use->next_use()) {
|
| - ASSERT(use->instruction()->IsMaterializeObject());
|
| - }
|
| -#endif
|
| - ASSERT(alloc->input_use_list() == NULL);
|
| - alloc->RemoveFromGraph();
|
| - if (alloc->ArgumentCount() > 0) {
|
| - ASSERT(alloc->ArgumentCount() == 1);
|
| - for (intptr_t i = 0; i < alloc->ArgumentCount(); ++i) {
|
| - alloc->PushArgumentAt(i)->RemoveFromGraph();
|
| - }
|
| - }
|
| -}
|
| -
|
| -
|
| -// Find allocation instructions that can be potentially eliminated and
|
| -// rematerialized at deoptimization exits if needed. See IsSafeUse
|
| -// for the description of algorithm used below.
|
| -void AllocationSinking::CollectCandidates() {
|
| - // Optimistically collect all potential candidates.
|
| - for (BlockIterator block_it = flow_graph_->reverse_postorder_iterator();
|
| - !block_it.Done();
|
| - block_it.Advance()) {
|
| - BlockEntryInstr* block = block_it.Current();
|
| - for (ForwardInstructionIterator it(block); !it.Done(); it.Advance()) {
|
| - { AllocateObjectInstr* alloc = it.Current()->AsAllocateObject();
|
| - if ((alloc != NULL) &&
|
| - IsAllocationSinkingCandidate(alloc, kOptimisticCheck)) {
|
| - alloc->SetIdentity(AliasIdentity::AllocationSinkingCandidate());
|
| - candidates_.Add(alloc);
|
| - }
|
| - }
|
| - { AllocateUninitializedContextInstr* alloc =
|
| - it.Current()->AsAllocateUninitializedContext();
|
| - if ((alloc != NULL) &&
|
| - IsAllocationSinkingCandidate(alloc, kOptimisticCheck)) {
|
| - alloc->SetIdentity(AliasIdentity::AllocationSinkingCandidate());
|
| - candidates_.Add(alloc);
|
| - }
|
| - }
|
| - }
|
| - }
|
| -
|
| - // Transitively unmark all candidates that are not strictly valid.
|
| - bool changed;
|
| - do {
|
| - changed = false;
|
| - for (intptr_t i = 0; i < candidates_.length(); i++) {
|
| - Definition* alloc = candidates_[i];
|
| - if (alloc->Identity().IsAllocationSinkingCandidate()) {
|
| - if (!IsAllocationSinkingCandidate(alloc, kStrictCheck)) {
|
| - alloc->SetIdentity(AliasIdentity::Unknown());
|
| - changed = true;
|
| - }
|
| - }
|
| - }
|
| - } while (changed);
|
| -
|
| - // Shrink the list of candidates removing all unmarked ones.
|
| - intptr_t j = 0;
|
| - for (intptr_t i = 0; i < candidates_.length(); i++) {
|
| - Definition* alloc = candidates_[i];
|
| - if (alloc->Identity().IsAllocationSinkingCandidate()) {
|
| - if (FLAG_trace_optimization) {
|
| - THR_Print("discovered allocation sinking candidate: v%" Pd "\n",
|
| - alloc->ssa_temp_index());
|
| - }
|
| -
|
| - if (j != i) {
|
| - candidates_[j] = alloc;
|
| - }
|
| - j++;
|
| - }
|
| - }
|
| - candidates_.TruncateTo(j);
|
| -}
|
| -
|
| -
|
| -// If materialization references an allocation sinking candidate then replace
|
| -// this reference with a materialization which should have been computed for
|
| -// this side-exit. CollectAllExits should have collected this exit.
|
| -void AllocationSinking::NormalizeMaterializations() {
|
| - for (intptr_t i = 0; i < candidates_.length(); i++) {
|
| - Definition* alloc = candidates_[i];
|
| -
|
| - Value* next_use;
|
| - for (Value* use = alloc->input_use_list();
|
| - use != NULL;
|
| - use = next_use) {
|
| - next_use = use->next_use();
|
| - if (use->instruction()->IsMaterializeObject()) {
|
| - use->BindTo(MaterializationFor(alloc, use->instruction()));
|
| - }
|
| - }
|
| - }
|
| -}
|
| -
|
| -
|
| -// We transitively insert materializations at each deoptimization exit that
|
| -// might see the given allocation (see ExitsCollector). Some of this
|
| -// materializations are not actually used and some fail to compute because
|
| -// they are inserted in the block that is not dominated by the allocation.
|
| -// Remove them unused materializations from the graph.
|
| -void AllocationSinking::RemoveUnusedMaterializations() {
|
| - intptr_t j = 0;
|
| - for (intptr_t i = 0; i < materializations_.length(); i++) {
|
| - MaterializeObjectInstr* mat = materializations_[i];
|
| - if ((mat->input_use_list() == NULL) && (mat->env_use_list() == NULL)) {
|
| - // Check if this materialization failed to compute and remove any
|
| - // unforwarded loads. There were no loads from any allocation sinking
|
| - // candidate in the beggining so it is safe to assume that any encountered
|
| - // load was inserted by CreateMaterializationAt.
|
| - for (intptr_t i = 0; i < mat->InputCount(); i++) {
|
| - LoadFieldInstr* load = mat->InputAt(i)->definition()->AsLoadField();
|
| - if ((load != NULL) &&
|
| - (load->instance()->definition() == mat->allocation())) {
|
| - load->ReplaceUsesWith(flow_graph_->constant_null());
|
| - load->RemoveFromGraph();
|
| - }
|
| - }
|
| - mat->RemoveFromGraph();
|
| - } else {
|
| - if (j != i) {
|
| - materializations_[j] = mat;
|
| - }
|
| - j++;
|
| - }
|
| - }
|
| - materializations_.TruncateTo(j);
|
| -}
|
| -
|
| -
|
| -// Some candidates might stop being eligible for allocation sinking after
|
| -// the load forwarding because they flow into phis that load forwarding
|
| -// inserts. Discover such allocations and remove them from the list
|
| -// of allocation sinking candidates undoing all changes that we did
|
| -// in preparation for sinking these allocations.
|
| -void AllocationSinking::DiscoverFailedCandidates() {
|
| - // Transitively unmark all candidates that are not strictly valid.
|
| - bool changed;
|
| - do {
|
| - changed = false;
|
| - for (intptr_t i = 0; i < candidates_.length(); i++) {
|
| - Definition* alloc = candidates_[i];
|
| - if (alloc->Identity().IsAllocationSinkingCandidate()) {
|
| - if (!IsAllocationSinkingCandidate(alloc, kStrictCheck)) {
|
| - alloc->SetIdentity(AliasIdentity::Unknown());
|
| - changed = true;
|
| - }
|
| - }
|
| - }
|
| - } while (changed);
|
| -
|
| - // Remove all failed candidates from the candidates list.
|
| - intptr_t j = 0;
|
| - for (intptr_t i = 0; i < candidates_.length(); i++) {
|
| - Definition* alloc = candidates_[i];
|
| - if (!alloc->Identity().IsAllocationSinkingCandidate()) {
|
| - if (FLAG_trace_optimization) {
|
| - THR_Print("allocation v%" Pd " can't be eliminated\n",
|
| - alloc->ssa_temp_index());
|
| - }
|
| -
|
| -#ifdef DEBUG
|
| - for (Value* use = alloc->env_use_list();
|
| - use != NULL;
|
| - use = use->next_use()) {
|
| - ASSERT(use->instruction()->IsMaterializeObject());
|
| - }
|
| -#endif
|
| -
|
| - // All materializations will be removed from the graph. Remove inserted
|
| - // loads first and detach materializations from allocation's environment
|
| - // use list: we will reconstruct it when we start removing
|
| - // materializations.
|
| - alloc->set_env_use_list(NULL);
|
| - for (Value* use = alloc->input_use_list();
|
| - use != NULL;
|
| - use = use->next_use()) {
|
| - if (use->instruction()->IsLoadField()) {
|
| - LoadFieldInstr* load = use->instruction()->AsLoadField();
|
| - load->ReplaceUsesWith(flow_graph_->constant_null());
|
| - load->RemoveFromGraph();
|
| - } else {
|
| - ASSERT(use->instruction()->IsMaterializeObject() ||
|
| - use->instruction()->IsPhi() ||
|
| - use->instruction()->IsStoreInstanceField());
|
| - }
|
| - }
|
| - } else {
|
| - if (j != i) {
|
| - candidates_[j] = alloc;
|
| - }
|
| - j++;
|
| - }
|
| - }
|
| -
|
| - if (j != candidates_.length()) { // Something was removed from candidates.
|
| - intptr_t k = 0;
|
| - for (intptr_t i = 0; i < materializations_.length(); i++) {
|
| - MaterializeObjectInstr* mat = materializations_[i];
|
| - if (!mat->allocation()->Identity().IsAllocationSinkingCandidate()) {
|
| - // Restore environment uses of the allocation that were replaced
|
| - // by this materialization and drop materialization.
|
| - mat->ReplaceUsesWith(mat->allocation());
|
| - mat->RemoveFromGraph();
|
| - } else {
|
| - if (k != i) {
|
| - materializations_[k] = mat;
|
| - }
|
| - k++;
|
| - }
|
| - }
|
| - materializations_.TruncateTo(k);
|
| - }
|
| -
|
| - candidates_.TruncateTo(j);
|
| -}
|
| -
|
| -
|
| -void AllocationSinking::Optimize() {
|
| - CollectCandidates();
|
| -
|
| - // Insert MaterializeObject instructions that will describe the state of the
|
| - // object at all deoptimization points. Each inserted materialization looks
|
| - // like this (where v_0 is allocation that we are going to eliminate):
|
| - // v_1 <- LoadField(v_0, field_1)
|
| - // ...
|
| - // v_N <- LoadField(v_0, field_N)
|
| - // v_{N+1} <- MaterializeObject(field_1 = v_1, ..., field_N = v_{N})
|
| - for (intptr_t i = 0; i < candidates_.length(); i++) {
|
| - InsertMaterializations(candidates_[i]);
|
| - }
|
| -
|
| - // Run load forwarding to eliminate LoadField instructions inserted above.
|
| - // All loads will be successfully eliminated because:
|
| - // a) they use fields (not offsets) and thus provide precise aliasing
|
| - // information
|
| - // b) candidate does not escape and thus its fields is not affected by
|
| - // external effects from calls.
|
| - LoadOptimizer::OptimizeGraph(flow_graph_);
|
| -
|
| - NormalizeMaterializations();
|
| -
|
| - RemoveUnusedMaterializations();
|
| -
|
| - // If any candidates are no longer eligible for allocation sinking abort
|
| - // the optimization for them and undo any changes we did in preparation.
|
| - DiscoverFailedCandidates();
|
| -
|
| - // At this point we have computed the state of object at each deoptimization
|
| - // point and we can eliminate it. Loads inserted above were forwarded so there
|
| - // are no uses of the allocation just as in the begging of the pass.
|
| - for (intptr_t i = 0; i < candidates_.length(); i++) {
|
| - EliminateAllocation(candidates_[i]);
|
| - }
|
| -
|
| - // Process materializations and unbox their arguments: materializations
|
| - // are part of the environment and can materialize boxes for double/mint/simd
|
| - // values when needed.
|
| - // TODO(vegorov): handle all box types here.
|
| - for (intptr_t i = 0; i < materializations_.length(); i++) {
|
| - MaterializeObjectInstr* mat = materializations_[i];
|
| - for (intptr_t j = 0; j < mat->InputCount(); j++) {
|
| - Definition* defn = mat->InputAt(j)->definition();
|
| - if (defn->IsBox()) {
|
| - mat->InputAt(j)->BindTo(defn->InputAt(0)->definition());
|
| - }
|
| - }
|
| - }
|
| -}
|
| -
|
| -
|
| -// Remove materializations from the graph. Register allocator will treat them
|
| -// as part of the environment not as a real instruction.
|
| -void AllocationSinking::DetachMaterializations() {
|
| - for (intptr_t i = 0; i < materializations_.length(); i++) {
|
| - materializations_[i]->previous()->LinkTo(materializations_[i]->next());
|
| - }
|
| -}
|
| -
|
| -
|
| -// Add a field/offset to the list of fields if it is not yet present there.
|
| -static bool AddSlot(ZoneGrowableArray<const Object*>* slots,
|
| - const Object& slot) {
|
| - for (intptr_t i = 0; i < slots->length(); i++) {
|
| - if ((*slots)[i]->raw() == slot.raw()) {
|
| - return false;
|
| - }
|
| - }
|
| - slots->Add(&slot);
|
| - return true;
|
| -}
|
| -
|
| -
|
| -// Find deoptimization exit for the given materialization assuming that all
|
| -// materializations are emitted right before the instruction which is a
|
| -// deoptimization exit.
|
| -static Instruction* ExitForMaterialization(MaterializeObjectInstr* mat) {
|
| - while (mat->next()->IsMaterializeObject()) {
|
| - mat = mat->next()->AsMaterializeObject();
|
| - }
|
| - return mat->next();
|
| -}
|
| -
|
| -
|
| -// Given the deoptimization exit find first materialization that was inserted
|
| -// before it.
|
| -static Instruction* FirstMaterializationAt(Instruction* exit) {
|
| - while (exit->previous()->IsMaterializeObject()) {
|
| - exit = exit->previous();
|
| - }
|
| - return exit;
|
| -}
|
| -
|
| -
|
| -// Given the allocation and deoptimization exit try to find MaterializeObject
|
| -// instruction corresponding to this allocation at this exit.
|
| -MaterializeObjectInstr* AllocationSinking::MaterializationFor(
|
| - Definition* alloc, Instruction* exit) {
|
| - if (exit->IsMaterializeObject()) {
|
| - exit = ExitForMaterialization(exit->AsMaterializeObject());
|
| - }
|
| -
|
| - for (MaterializeObjectInstr* mat = exit->previous()->AsMaterializeObject();
|
| - mat != NULL;
|
| - mat = mat->previous()->AsMaterializeObject()) {
|
| - if (mat->allocation() == alloc) {
|
| - return mat;
|
| - }
|
| - }
|
| -
|
| - return NULL;
|
| -}
|
| -
|
| -
|
| -// Insert MaterializeObject instruction for the given allocation before
|
| -// the given instruction that can deoptimize.
|
| -void AllocationSinking::CreateMaterializationAt(
|
| - Instruction* exit,
|
| - Definition* alloc,
|
| - const ZoneGrowableArray<const Object*>& slots) {
|
| - ZoneGrowableArray<Value*>* values =
|
| - new(Z) ZoneGrowableArray<Value*>(slots.length());
|
| -
|
| - // All loads should be inserted before the first materialization so that
|
| - // IR follows the following pattern: loads, materializations, deoptimizing
|
| - // instruction.
|
| - Instruction* load_point = FirstMaterializationAt(exit);
|
| -
|
| - // Insert load instruction for every field.
|
| - for (intptr_t i = 0; i < slots.length(); i++) {
|
| - LoadFieldInstr* load = slots[i]->IsField()
|
| - ? new(Z) LoadFieldInstr(
|
| - new(Z) Value(alloc),
|
| - &Field::Cast(*slots[i]),
|
| - AbstractType::ZoneHandle(Z),
|
| - alloc->token_pos())
|
| - : new(Z) LoadFieldInstr(
|
| - new(Z) Value(alloc),
|
| - Smi::Cast(*slots[i]).Value(),
|
| - AbstractType::ZoneHandle(Z),
|
| - alloc->token_pos());
|
| - flow_graph_->InsertBefore(
|
| - load_point, load, NULL, FlowGraph::kValue);
|
| - values->Add(new(Z) Value(load));
|
| - }
|
| -
|
| - MaterializeObjectInstr* mat = NULL;
|
| - if (alloc->IsAllocateObject()) {
|
| - mat = new(Z) MaterializeObjectInstr(
|
| - alloc->AsAllocateObject(), slots, values);
|
| - } else {
|
| - ASSERT(alloc->IsAllocateUninitializedContext());
|
| - mat = new(Z) MaterializeObjectInstr(
|
| - alloc->AsAllocateUninitializedContext(), slots, values);
|
| - }
|
| -
|
| - flow_graph_->InsertBefore(exit, mat, NULL, FlowGraph::kValue);
|
| -
|
| - // Replace all mentions of this allocation with a newly inserted
|
| - // MaterializeObject instruction.
|
| - // We must preserve the identity: all mentions are replaced by the same
|
| - // materialization.
|
| - for (Environment::DeepIterator env_it(exit->env());
|
| - !env_it.Done();
|
| - env_it.Advance()) {
|
| - Value* use = env_it.CurrentValue();
|
| - if (use->definition() == alloc) {
|
| - use->RemoveFromUseList();
|
| - use->set_definition(mat);
|
| - mat->AddEnvUse(use);
|
| - }
|
| - }
|
| -
|
| - // Mark MaterializeObject as an environment use of this allocation.
|
| - // This will allow us to discover it when we are looking for deoptimization
|
| - // exits for another allocation that potentially flows into this one.
|
| - Value* val = new(Z) Value(alloc);
|
| - val->set_instruction(mat);
|
| - alloc->AddEnvUse(val);
|
| -
|
| - // Record inserted materialization.
|
| - materializations_.Add(mat);
|
| -}
|
| -
|
| -
|
| -// Add given instruction to the list of the instructions if it is not yet
|
| -// present there.
|
| -template<typename T>
|
| -void AddInstruction(GrowableArray<T*>* list, T* value) {
|
| - ASSERT(!value->IsGraphEntry());
|
| - for (intptr_t i = 0; i < list->length(); i++) {
|
| - if ((*list)[i] == value) {
|
| - return;
|
| - }
|
| - }
|
| - list->Add(value);
|
| -}
|
| -
|
| -
|
| -// Transitively collect all deoptimization exits that might need this allocation
|
| -// rematerialized. It is not enough to collect only environment uses of this
|
| -// allocation because it can flow into other objects that will be
|
| -// dematerialized and that are referenced by deopt environments that
|
| -// don't contain this allocation explicitly.
|
| -void AllocationSinking::ExitsCollector::Collect(Definition* alloc) {
|
| - for (Value* use = alloc->env_use_list();
|
| - use != NULL;
|
| - use = use->next_use()) {
|
| - if (use->instruction()->IsMaterializeObject()) {
|
| - AddInstruction(&exits_, ExitForMaterialization(
|
| - use->instruction()->AsMaterializeObject()));
|
| - } else {
|
| - AddInstruction(&exits_, use->instruction());
|
| - }
|
| - }
|
| -
|
| - // Check if this allocation is stored into any other allocation sinking
|
| - // candidate and put it on worklist so that we conservatively collect all
|
| - // exits for that candidate as well because they potentially might see
|
| - // this object.
|
| - for (Value* use = alloc->input_use_list();
|
| - use != NULL;
|
| - use = use->next_use()) {
|
| - Definition* obj = StoreInto(use);
|
| - if ((obj != NULL) && (obj != alloc)) {
|
| - AddInstruction(&worklist_, obj);
|
| - }
|
| - }
|
| -}
|
| -
|
| -
|
| -void AllocationSinking::ExitsCollector::CollectTransitively(Definition* alloc) {
|
| - exits_.TruncateTo(0);
|
| - worklist_.TruncateTo(0);
|
| -
|
| - worklist_.Add(alloc);
|
| -
|
| - // Note: worklist potentially will grow while we are iterating over it.
|
| - // We are not removing allocations from the worklist not to waste space on
|
| - // the side maintaining BitVector of already processed allocations: worklist
|
| - // is expected to be very small thus linear search in it is just as effecient
|
| - // as a bitvector.
|
| - for (intptr_t i = 0; i < worklist_.length(); i++) {
|
| - Collect(worklist_[i]);
|
| - }
|
| -}
|
| -
|
| -
|
| -void AllocationSinking::InsertMaterializations(Definition* alloc) {
|
| - // Collect all fields that are written for this instance.
|
| - ZoneGrowableArray<const Object*>* slots =
|
| - new(Z) ZoneGrowableArray<const Object*>(5);
|
| -
|
| - for (Value* use = alloc->input_use_list();
|
| - use != NULL;
|
| - use = use->next_use()) {
|
| - StoreInstanceFieldInstr* store = use->instruction()->AsStoreInstanceField();
|
| - if ((store != NULL) && (store->instance()->definition() == alloc)) {
|
| - if (!store->field().IsNull()) {
|
| - AddSlot(slots, store->field());
|
| - } else {
|
| - AddSlot(slots, Smi::ZoneHandle(Z, Smi::New(store->offset_in_bytes())));
|
| - }
|
| - }
|
| - }
|
| -
|
| - if (alloc->ArgumentCount() > 0) {
|
| - AllocateObjectInstr* alloc_object = alloc->AsAllocateObject();
|
| - ASSERT(alloc_object->ArgumentCount() == 1);
|
| - intptr_t type_args_offset =
|
| - alloc_object->cls().type_arguments_field_offset();
|
| - AddSlot(slots, Smi::ZoneHandle(Z, Smi::New(type_args_offset)));
|
| - }
|
| -
|
| - // Collect all instructions that mention this object in the environment.
|
| - exits_collector_.CollectTransitively(alloc);
|
| -
|
| - // Insert materializations at environment uses.
|
| - for (intptr_t i = 0; i < exits_collector_.exits().length(); i++) {
|
| - CreateMaterializationAt(
|
| - exits_collector_.exits()[i], alloc, *slots);
|
| - }
|
| }
|
|
|
|
|
|
|