Implement simple dead store elimination.

This optimization is the dual to load elimination. It uses
the same infrastructure for handling aliasing. Since dead
store elimination is a backward data-flow analysis it inherits
from LivenessAnalysis.

Instead of eliminating upward exposed loads, it eliminates
downward exposed stores.

First local intra-block analysis is done in ComputeInitialSets.
Afte the fixed point iteration EliminateDeadStores performs the
global optimization on downward exposed stores in each block.

Only fully dead stores are eliminated. No partially dead
stores yet.

Example:

1: o.x = null;
2: if (cond) {
3:   o.x = 1;
4: } else {
5:   o.x = 2;
6: }

The store in line 1 is fully dead and will be removed. Note that
any deoptimization in "cond" will make the store only partially
dead and it won't be removed yet.

R=vegorov@google.com

Review URL: https://codereview.chromium.org//143263010

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@35909 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
fschneider@google.com 2014-05-08 14:26:16 +00:00
parent 3c4d542b92
commit 16e03db724
6 changed files with 385 additions and 25 deletions

View file

@ -438,12 +438,8 @@ static bool CompileParsedFunctionHelper(ParsedFunction* parsed_function,
}
}
// Optimize (a << b) & c patterns, merge operations.
// Run after CSE in order to have more opportunity to merge
// instructions that have same inputs.
optimizer.TryOptimizePatterns();
DEBUG_ASSERT(flow_graph->VerifyUseLists());
// Run loop-invariant code motion right after load elimination since it
// depends on the numbering of loads from the previous load-elimination.
if (FLAG_loop_invariant_code_motion) {
LICM licm(flow_graph);
licm.Optimize();
@ -451,6 +447,14 @@ static bool CompileParsedFunctionHelper(ParsedFunction* parsed_function,
}
flow_graph->RemoveRedefinitions();
// Optimize (a << b) & c patterns, merge operations.
// Run after CSE in order to have more opportunity to merge
// instructions that have same inputs.
optimizer.TryOptimizePatterns();
DEBUG_ASSERT(flow_graph->VerifyUseLists());
DeadStoreElimination::Optimize(flow_graph);
if (FLAG_range_analysis) {
// Propagate types after store-load-forwarding. Some phis may have
// become smi phis that can be processed by range analysis.

View file

@ -27,6 +27,7 @@ DEFINE_FLAG(bool, array_bounds_check_elimination, true,
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::VisitInstanceCall(InstanceCallInstr* instr) {
}
}
void FlowGraphOptimizer::VisitStaticCall(StaticCallInstr* call) {
MethodRecognizer::Kind recognized_kind =
MethodRecognizer::RecognizeKind(call->function());
@ -5101,7 +5103,7 @@ void LICM::TryHoistCheckSmiThroughPhi(ForwardInstructionIterator* it,
// 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 IsCandidateLoad(Instruction* instr) {
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 @@ void LICM::Optimize() {
}
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 @@ class Place : public ValueObject {
// 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 @@ class Place : public ValueObject {
kind_ = kVMField;
offset_in_bytes_ = store->offset_in_bytes();
}
*is_store = true;
break;
}
@ -5365,6 +5360,7 @@ class Place : public ValueObject {
representation_ = instr->AsStoreStaticField()->
RequiredInputRepresentation(StoreStaticFieldInstr::kValuePos);
field_ = &instr->AsStoreStaticField()->field();
*is_store = true;
break;
case Instruction::kLoadIndexed: {
@ -5384,6 +5380,7 @@ class Place : public ValueObject {
RequiredInputRepresentation(StoreIndexedInstr::kValuePos);
instance_ = store_indexed->array()->definition()->OriginalDefinition();
index_ = store_indexed->index()->definition();
*is_store = true;
break;
}
@ -5399,6 +5396,7 @@ class Place : public ValueObject {
ASSERT(instr->AsStoreContext()->RequiredInputRepresentation(
StoreContextInstr::kValuePos) == kTagged);
representation_ = kTagged;
*is_store = true;
break;
default:
@ -6172,14 +6170,23 @@ static PhiPlaceMoves* ComputePhiMoves(
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 @@ static AliasedSet* NumberPlaces(
!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,7 +6218,10 @@ static AliasedSet* NumberPlaces(
}
}
if (!has_loads) {
if ((mode == kOptimizeLoads) && !has_loads) {
return NULL;
}
if ((mode == kOptimizeStores) && !has_stores) {
return NULL;
}
@ -6268,7 +6277,7 @@ class LoadOptimizer : public ValueObject {
}
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 @@ class LoadOptimizer : public ValueObject {
(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 LoadOptimizer : public ValueObject {
};
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

View file

@ -288,6 +288,12 @@ class DominatorBasedCSE : public AllStatic {
};
class DeadStoreElimination : public AllStatic {
public:
static void Optimize(FlowGraph* graph);
};
// Sparse conditional constant propagation and unreachable code elimination.
// Assumes that use lists are computed and preserves them.
class ConstantPropagator : public FlowGraphVisitor {

View file

@ -643,6 +643,11 @@ void ForwardInstructionIterator::RemoveCurrentFromGraph() {
}
void BackwardInstructionIterator::RemoveCurrentFromGraph() {
current_ = current_->RemoveFromGraph(false); // Set current_ to next.
}
// Default implementation of visiting basic blocks. Can be overridden.
void FlowGraphVisitor::VisitBlocks() {
ASSERT(current_iterator_ == NULL);

View file

@ -1473,6 +1473,8 @@ class BackwardInstructionIterator : public ValueObject {
bool Done() const { return current_ == block_entry_; }
void RemoveCurrentFromGraph();
Instruction* Current() const { return current_; }
private:
@ -3758,6 +3760,7 @@ class StoreInstanceFieldInstr : public TemplateDefinition<2> {
Value* instance() const { return inputs_[kInstancePos]; }
Value* value() const { return inputs_[kValuePos]; }
bool is_initialization() const { return is_initialization_; }
virtual intptr_t token_pos() const { return token_pos_; }
virtual CompileType* ComputeInitialType() const;

View file

@ -0,0 +1,122 @@
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// Test correctness of side effects tracking used by load to load forwarding.
// VMOptions=--optimization-counter-threshold=10
import "package:expect/expect.dart";
class C {
var x;
var y;
final z = 123;
}
class D {
var x = 0.0;
}
var array = [0, 0];
s1(a) {
a.x = 42;
a.x = 43;
return a.x;
}
void foo(a) {
Expect.equals(42, a.x);
}
s1a(a) {
a.x = 42;
foo(a);
a.x = 43;
return a.x;
}
s2() {
var t = new C();
return t;
}
s3(a, b) {
a.x = b + 1;
if (b % 2 == 0) {
a.x = 0;
} else {
a.x = 0;
}
return a.x;
}
s4(a, b) {
a.x = b + 1.0;
if (b % 2 == 0) {
a.x = b + 2.0;
}
a.x = b + 1.0;
return a.x;
}
test_with_context() {
f(a) {
var b = a + 1;
return (() => b + 1)();
}
for (var i = 0; i < 100000; i++) f(42);
Expect.equals(44, f(42));
}
test_with_instance() {
for (var i = 0; i < 20; i++) Expect.equals(43, s1(new C()));
for (var i = 0; i < 20; i++) Expect.equals(43, s1a(new C()));
for (var i = 0; i < 20; i++) Expect.equals(123, s2().z);
for (var i = 0; i < 20; i++) Expect.equals(0, s3(new C(), i));
for (var i = 0; i < 20; i++) Expect.equals(i + 1.0, s4(new D(), i));
}
arr1(a) {
a[0] = 42;
a[0] = 43;
Expect.equals(a[0], 43);
return a[0];
}
arr2(a, b) {
a[0] = 42;
a[b % 2] = 43;
Expect.equals(a[b % 2], 43);
return a[0];
}
test_with_array() {
for (var i = 0; i < 20; i++) Expect.equals(43, arr1(array));
for (var i = 0; i < 20; i++) {
Expect.equals(i % 2 == 0 ? 43 : 42, arr2(array, i));
}
}
var st = 0;
static1(b) {
st = 42;
if (b % 2 == 0) {
st = 2;
}
st = b + 1;
Expect.equals(st, b + 1);
return st;
}
test_with_static() {
for (var i = 0; i < 20; i++) Expect.equals(i + 1, static1(i));
}
main() {
test_with_instance();
test_with_array();
test_with_context();
test_with_static();
}