| Index: runtime/vm/flow_graph_optimizer.cc
|
| ===================================================================
|
| --- runtime/vm/flow_graph_optimizer.cc (revision 35904)
|
| +++ runtime/vm/flow_graph_optimizer.cc (working copy)
|
| @@ -27,6 +27,7 @@
|
| DEFINE_FLAG(int, getter_setter_ratio, 13,
|
| "Ratio of getter/setter usage used for double field unboxing heuristics");
|
| 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,
|
| @@ -4098,6 +4099,7 @@
|
| }
|
| }
|
|
|
| +
|
| void FlowGraphOptimizer::VisitStaticCall(StaticCallInstr* call) {
|
| MethodRecognizer::Kind recognized_kind =
|
| MethodRecognizer::RecognizeKind(call->function());
|
| @@ -5101,7 +5103,7 @@
|
|
|
|
|
| // Load instructions handled by load elimination.
|
| -static bool IsCandidateLoad(Instruction* instr) {
|
| +static bool IsLoadEliminationCandidate(Instruction* instr) {
|
| return instr->IsLoadField()
|
| || instr->IsLoadIndexed()
|
| || instr->IsLoadStaticField()
|
| @@ -5112,7 +5114,7 @@
|
| static bool IsLoopInvariantLoad(ZoneGrowableArray<BitVector*>* sets,
|
| intptr_t loop_header_index,
|
| Instruction* instr) {
|
| - return IsCandidateLoad(instr) &&
|
| + return IsLoadEliminationCandidate(instr) &&
|
| (sets != NULL) &&
|
| instr->HasPlaceId() &&
|
| ((*sets)[loop_header_index] != NULL) &&
|
| @@ -5178,14 +5180,6 @@
|
| }
|
|
|
|
|
| -static bool IsLoadEliminationCandidate(Definition* def) {
|
| - return def->IsLoadField()
|
| - || def->IsLoadIndexed()
|
| - || def->IsLoadStaticField()
|
| - || def->IsCurrentContext();
|
| -}
|
| -
|
| -
|
| // Alias represents a family of locations. It is used to capture aliasing
|
| // between stores and loads. Store can alias another load or store if and only
|
| // if they have the same alias.
|
| @@ -5315,7 +5309,7 @@
|
|
|
| // Construct a place from instruction if instruction accesses any place.
|
| // Otherwise constructs kNone place.
|
| - Place(Instruction* instr, bool* is_load)
|
| + Place(Instruction* instr, bool* is_load, bool* is_store)
|
| : kind_(kNone),
|
| representation_(kNoRepresentation),
|
| instance_(NULL),
|
| @@ -5350,6 +5344,7 @@
|
| kind_ = kVMField;
|
| offset_in_bytes_ = store->offset_in_bytes();
|
| }
|
| + *is_store = true;
|
| break;
|
| }
|
|
|
| @@ -5365,6 +5360,7 @@
|
| representation_ = instr->AsStoreStaticField()->
|
| RequiredInputRepresentation(StoreStaticFieldInstr::kValuePos);
|
| field_ = &instr->AsStoreStaticField()->field();
|
| + *is_store = true;
|
| break;
|
|
|
| case Instruction::kLoadIndexed: {
|
| @@ -5384,6 +5380,7 @@
|
| RequiredInputRepresentation(StoreIndexedInstr::kValuePos);
|
| instance_ = store_indexed->array()->definition()->OriginalDefinition();
|
| index_ = store_indexed->index()->definition();
|
| + *is_store = true;
|
| break;
|
| }
|
|
|
| @@ -5399,6 +5396,7 @@
|
| ASSERT(instr->AsStoreContext()->RequiredInputRepresentation(
|
| StoreContextInstr::kValuePos) == kTagged);
|
| representation_ = kTagged;
|
| + *is_store = true;
|
| break;
|
|
|
| default:
|
| @@ -6172,14 +6170,23 @@
|
| return phi_moves;
|
| }
|
|
|
| +
|
| +enum CSEMode {
|
| + kOptimizeLoads,
|
| + kOptimizeStores
|
| +};
|
| +
|
| +
|
| static AliasedSet* NumberPlaces(
|
| FlowGraph* graph,
|
| - DirectChainedHashMap<PointerKeyValueTrait<Place> >* map) {
|
| + DirectChainedHashMap<PointerKeyValueTrait<Place> >* map,
|
| + CSEMode mode) {
|
| // Loads representing different expression ids will be collected and
|
| // used to build per offset kill sets.
|
| ZoneGrowableArray<Place*>* places = new ZoneGrowableArray<Place*>(10);
|
|
|
| bool has_loads = false;
|
| + bool has_stores = false;
|
| for (BlockIterator it = graph->reverse_postorder_iterator();
|
| !it.Done();
|
| it.Advance()) {
|
| @@ -6188,8 +6195,7 @@
|
| !instr_it.Done();
|
| instr_it.Advance()) {
|
| Instruction* instr = instr_it.Current();
|
| -
|
| - Place place(instr, &has_loads);
|
| + Place place(instr, &has_loads, &has_stores);
|
| if (place.kind() == Place::kNone) {
|
| continue;
|
| }
|
| @@ -6212,9 +6218,12 @@
|
| }
|
| }
|
|
|
| - if (!has_loads) {
|
| + if ((mode == kOptimizeLoads) && !has_loads) {
|
| return NULL;
|
| }
|
| + if ((mode == kOptimizeStores) && !has_stores) {
|
| + return NULL;
|
| + }
|
|
|
| PhiPlaceMoves* phi_moves = ComputePhiMoves(map, places);
|
|
|
| @@ -6268,7 +6277,7 @@
|
| }
|
|
|
| DirectChainedHashMap<PointerKeyValueTrait<Place> > map;
|
| - AliasedSet* aliased_set = NumberPlaces(graph, &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.
|
| @@ -6346,9 +6355,9 @@
|
| (array_store->class_id() == kTypedDataFloat64ArrayCid) ||
|
| (array_store->class_id() == kTypedDataFloat32ArrayCid) ||
|
| (array_store->class_id() == kTypedDataFloat32x4ArrayCid)) {
|
| - bool is_load = false;
|
| - Place store_place(instr, &is_load);
|
| - ASSERT(!is_load);
|
| + bool is_load = false, is_store = false;
|
| + Place store_place(instr, &is_load, &is_store);
|
| + ASSERT(!is_load && is_store);
|
| Place* place = map_->Lookup(&store_place);
|
| if (place != NULL) {
|
| // Store has a corresponding numbered place that might have a
|
| @@ -7048,6 +7057,217 @@
|
| };
|
|
|
|
|
| +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:
|
| + if (instr->AsStoreInstanceField()->is_initialization()) {
|
| + // Can't eliminate stores that initialized unboxed fields.
|
| + return false;
|
| + }
|
| + case Instruction::kStoreContext:
|
| + case Instruction::kStoreIndexed:
|
| + case Instruction::kStoreStaticField:
|
| + return true;
|
| + default:
|
| + UNREACHABLE();
|
| + return false;
|
| + }
|
| + }
|
| +
|
| + virtual void ComputeInitialSets() {
|
| + BitVector* all_places = new BitVector(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.IsFinalField()) {
|
| + // 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) {
|
| + OS::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 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 Alias alias = aliased_set_->ComputeAlias(&place);
|
| + live_in->AddAll(aliased_set_->Get(alias));
|
| + continue;
|
| + }
|
| + }
|
| + exposed_stores_[postorder_number] = exposed_stores;
|
| + }
|
| + if (FLAG_trace_load_optimization) {
|
| + Dump();
|
| + OS::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.IsFinalField()) {
|
| + // 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) {
|
| + OS::Print("Removing dead store to place %"Pd" in 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);
|
| + }
|
| +}
|
| +
|
| +
|
| class CSEInstructionMap : public ValueObject {
|
| public:
|
| // Right now CSE and LICM track a single effect: possible externalization of
|
|
|