[vm/compiler] induction variable analysis
Rationale:
The more one knows about loops, the better!
This introduces a rigorous framework for detecting
induction variables (more precise "sequence" variables).
Even though this is already pretty general, in the
future we can expand this on a "need to" base, recognizing
more operators (shifts, negate, etc.) and more classes
of induction (perhaps geometric, polynomial, etc.).
Change-Id: I82a14515e8ae946d520ee470cd31046f6a58af7d
Reviewed-on: https://dart-review.googlesource.com/c/81743
Commit-Queue: Aart Bik <ajcbik@google.com>
Reviewed-by: Vyacheslav Egorov <vegorov@google.com>
diff --git a/runtime/vm/compiler/backend/loops.cc b/runtime/vm/compiler/backend/loops.cc
index 42e656e..394da9e 100644
--- a/runtime/vm/compiler/backend/loops.cc
+++ b/runtime/vm/compiler/backend/loops.cc
@@ -11,11 +11,624 @@
namespace dart {
+// Private class to perform induction variable analysis on a single loop
+// or a full loop hierarchy. The analysis implementation is based on the
+// paper by M. Gerlek et al. "Beyond Induction Variables: Detecting and
+// Classifying Sequences Using a Demand-Driven SSA Form" (ACM Transactions
+// on Programming Languages and Systems, Volume 17 Issue 1, Jan. 1995).
+//
+// The algorithm discovers and classifies definitions within loops that
+// behave like induction variables, and attaches an InductionVar record
+// to it (this mapping is stored in the loop data structure). The algorithm
+// first finds strongly connected components in the flow graph and classifies
+// each component as an induction when possible. Due to the descendant-first
+// nature, classification happens "on-demand" (e.g. basic induction is
+// classified before derived induction).
+class InductionVarAnalysis : public ValueObject {
+ public:
+ // Constructor to set up analysis phase.
+ explicit InductionVarAnalysis(const GrowableArray<BlockEntryInstr*>& preorder)
+ : preorder_(preorder),
+ stack_(),
+ scc_(),
+ cycle_(),
+ map_(),
+ current_index_(0),
+ zone_(Thread::Current()->zone()) {}
+
+ // Detects induction variables on the full loop hierarchy.
+ void VisitHierarchy(LoopInfo* loop);
+
+ // Detects induction variables on a single loop.
+ void VisitLoop(LoopInfo* loop);
+
+ private:
+ // An information node needed during SCC traversal that can
+ // reside in a map without any explicit memory allocation.
+ struct SCCInfo {
+ SCCInfo() : depth(-1), done(false) {}
+ explicit SCCInfo(intptr_t d) : depth(d), done(false) {}
+ intptr_t depth;
+ bool done;
+ bool operator!=(const SCCInfo& other) const {
+ return depth != other.depth || done != other.done;
+ }
+ bool operator==(const SCCInfo& other) const {
+ return depth == other.depth && done == other.done;
+ }
+ };
+ typedef RawPointerKeyValueTrait<Definition, SCCInfo> VisitKV;
+
+ // Traversal methods.
+ bool Visit(LoopInfo* loop, Definition* def);
+ intptr_t VisitDescendant(LoopInfo* loop, Definition* def);
+ void Classify(LoopInfo* loop, Definition* def);
+ void ClassifySCC(LoopInfo* loop);
+
+ // Transfer methods. Computes how induction of the operands, if any,
+ // tranfers over the operation performed by the given definition.
+ InductionVar* TransferPhi(LoopInfo* loop, Definition* def, intptr_t idx = -1);
+ InductionVar* TransferBinary(LoopInfo* loop, Definition* def);
+ InductionVar* TransferUnary(LoopInfo* loop, Definition* def);
+
+ // Solver methods. Computes how temporary meaning given to the
+ // definitions in a cycle transfer over the operation performed
+ // by the given definition.
+ InductionVar* SolvePhi(LoopInfo* loop, Definition* def, intptr_t idx = -1);
+ InductionVar* SolveBinary(LoopInfo* loop,
+ Definition* def,
+ InductionVar* init);
+ InductionVar* SolveUnary(LoopInfo* loop, Definition* def, InductionVar* init);
+
+ // Lookup.
+ InductionVar* Lookup(LoopInfo* loop, Definition* def);
+ InductionVar* LookupCycle(Definition* def);
+
+ // Arithmetic.
+ InductionVar* Add(InductionVar* x, InductionVar* y);
+ InductionVar* Sub(InductionVar* x, InductionVar* y);
+ InductionVar* Mul(InductionVar* x, InductionVar* y);
+
+ // Bookkeeping data (released when analysis goes out of scope).
+ const GrowableArray<BlockEntryInstr*>& preorder_;
+ GrowableArray<Definition*> stack_;
+ GrowableArray<Definition*> scc_;
+ DirectChainedHashMap<LoopInfo::InductionKV> cycle_;
+ DirectChainedHashMap<VisitKV> map_;
+ intptr_t current_index_;
+ Zone* zone_;
+
+ DISALLOW_COPY_AND_ASSIGN(InductionVarAnalysis);
+};
+
+// Helper method that finds phi-index of the initial value
+// that comes from a block outside the loop. Note that the
+// algorithm still works if there are several of these.
+static intptr_t InitIndex(LoopInfo* loop) {
+ BlockEntryInstr* header = loop->header();
+ for (intptr_t i = 0; i < header->PredecessorCount(); ++i) {
+ if (!loop->Contains(header->PredecessorAt(i))) { // pick first
+ return i;
+ }
+ }
+ UNREACHABLE();
+ return -1;
+}
+
+// Helper method that determines if a definition is a constant.
+static bool IsConstant(Definition* def, int64_t* val) {
+ if (def->IsConstant()) {
+ const Object& value = def->AsConstant()->value();
+ if (value.IsInteger()) {
+ *val = Integer::Cast(value).AsInt64Value(); // smi and mint
+ return true;
+ }
+ }
+ return false;
+}
+
+void InductionVarAnalysis::VisitHierarchy(LoopInfo* loop) {
+ for (; loop != nullptr; loop = loop->next_) {
+ VisitLoop(loop);
+ VisitHierarchy(loop->inner_);
+ }
+}
+
+void InductionVarAnalysis::VisitLoop(LoopInfo* loop) {
+ loop->ResetInduction();
+ // Find strongly connected components (SSCs) in the SSA graph of this
+ // loop using Tarjan's algorithm. Due to the descendant-first nature,
+ // classification happens "on-demand".
+ current_index_ = 0;
+ ASSERT(stack_.is_empty());
+ ASSERT(map_.IsEmpty());
+ for (BitVector::Iterator it(loop->blocks_); !it.Done(); it.Advance()) {
+ BlockEntryInstr* block = preorder_[it.Current()];
+ ASSERT(block->loop_info() != nullptr);
+ if (block->loop_info() != loop) {
+ continue; // inner loop
+ }
+ // Visit phi-operations.
+ if (block->IsJoinEntry()) {
+ for (PhiIterator it(block->AsJoinEntry()); !it.Done(); it.Advance()) {
+ Visit(loop, it.Current());
+ }
+ }
+ // Visit instructions.
+ for (ForwardInstructionIterator it(block); !it.Done(); it.Advance()) {
+ Visit(loop, it.Current()->AsDefinition());
+ }
+ }
+ ASSERT(stack_.is_empty());
+ map_.Clear();
+}
+
+bool InductionVarAnalysis::Visit(LoopInfo* loop, Definition* def) {
+ if (def == nullptr || map_.HasKey(def)) {
+ return false; // no def, or already visited
+ }
+ intptr_t d = ++current_index_;
+ map_.Insert(VisitKV::Pair(def, SCCInfo(d)));
+ stack_.Add(def);
+
+ // Visit all descendants.
+ intptr_t low = d;
+ for (intptr_t i = 0, n = def->InputCount(); i < n; i++) {
+ Value* input = def->InputAt(i);
+ if (input != nullptr) {
+ low = Utils::Minimum(low, VisitDescendant(loop, input->definition()));
+ }
+ }
+
+ // Lower or found SCC?
+ if (low < d) {
+ map_.Lookup(def)->value.depth = low;
+ } else {
+ // Pop the stack to build the SCC for classification.
+ ASSERT(scc_.is_empty());
+ while (!stack_.is_empty()) {
+ Definition* top = stack_.RemoveLast();
+ scc_.Add(top);
+ map_.Lookup(top)->value.done = true;
+ if (top == def) {
+ break;
+ }
+ }
+ // Classify.
+ if (scc_.length() == 1) {
+ Classify(loop, scc_[0]);
+ } else {
+ ASSERT(scc_.length() > 1);
+ ASSERT(cycle_.IsEmpty());
+ ClassifySCC(loop);
+ cycle_.Clear();
+ }
+ scc_.Clear();
+ }
+ return true;
+}
+
+intptr_t InductionVarAnalysis::VisitDescendant(LoopInfo* loop,
+ Definition* def) {
+ // The traversal stops at anything not defined in this loop
+ // (either a loop invariant entry value defined outside the
+ // loop or an inner exit value defined by an inner loop).
+ if (def->GetBlock()->loop_info() != loop) {
+ return current_index_;
+ }
+ // Inspect descendant node.
+ if (!Visit(loop, def) && map_.Lookup(def)->value.done) {
+ return current_index_;
+ }
+ return map_.Lookup(def)->value.depth;
+}
+
+void InductionVarAnalysis::Classify(LoopInfo* loop, Definition* def) {
+ // Classify different kind of instructions.
+ InductionVar* induc = nullptr;
+ if (loop->IsHeaderPhi(def)) {
+ intptr_t idx = InitIndex(loop);
+ induc = TransferPhi(loop, def, idx);
+ if (induc != nullptr) {
+ InductionVar* init = Lookup(loop, def->InputAt(idx)->definition());
+ // Wrap-around (except for unusual header phi(x,..,x) = x).
+ if (!init->IsEqual(induc)) {
+ induc =
+ new (zone_) InductionVar(InductionVar::kWrapAround, init, induc);
+ }
+ }
+ } else if (def->IsPhi()) {
+ induc = TransferPhi(loop, def);
+ } else if (def->IsRedefinition() || def->IsConstraint() || def->IsBox() ||
+ def->IsUnbox()) {
+ induc = Lookup(loop, def->InputAt(0)->definition()); // pass-through
+ } else if (def->IsBinaryIntegerOp()) {
+ induc = TransferBinary(loop, def);
+ } else if (def->IsUnaryIntegerOp()) {
+ induc = TransferUnary(loop, def);
+ }
+ // Successfully classified?
+ if (induc != nullptr) {
+ loop->AddInduction(def, induc);
+ }
+}
+
+void InductionVarAnalysis::ClassifySCC(LoopInfo* loop) {
+ intptr_t size = scc_.length();
+ // Find a header phi, usually at the end.
+ intptr_t p = -1;
+ for (intptr_t i = size - 1; i >= 0; i--) {
+ if (loop->IsHeaderPhi(scc_[i])) {
+ p = i;
+ break;
+ }
+ }
+ // Rotate header phi up front.
+ if (p >= 0) {
+ Definition* phi = scc_[p];
+ intptr_t idx = InitIndex(loop);
+ InductionVar* init = Lookup(loop, phi->InputAt(idx)->definition());
+ // Inspect remainder of the cycle. The cycle mapping assigns temporary
+ // meaning to instructions, seeded from the phi instruction and back.
+ // The init of the phi is passed as marker token to detect first use.
+ cycle_.Insert(LoopInfo::InductionKV::Pair(phi, init));
+ for (intptr_t i = 1, j = p; i < size; i++) {
+ if (++j >= size) j = 0;
+ Definition* def = scc_[j];
+ InductionVar* update = nullptr;
+ if (def->IsPhi()) {
+ update = SolvePhi(loop, def);
+ } else if (def->IsRedefinition() || def->IsConstraint() || def->IsBox() ||
+ def->IsUnbox()) {
+ update = LookupCycle(def->InputAt(0)->definition()); // pass-through
+ } else if (def->IsBinaryIntegerOp()) {
+ update = SolveBinary(loop, def, init);
+ } else if (def->IsUnaryIntegerOp()) {
+ update = SolveUnary(loop, def, init);
+ }
+ // Continue cycle?
+ if (update == nullptr) {
+ return;
+ }
+ cycle_.Insert(LoopInfo::InductionKV::Pair(def, update));
+ }
+ // Success if all internal links (inputs to the phi that are along
+ // back-edges) received the same temporary meaning. The external
+ // link (initial value coming from outside the loop) is excluded
+ // while taking this join.
+ InductionVar* induc = SolvePhi(loop, phi, idx);
+ if (induc != nullptr) {
+ // Invariant means linear induction.
+ if (induc->kind_ == InductionVar::kInvariant) {
+ induc = new (zone_) InductionVar(InductionVar::kLinear, init, induc);
+ } else {
+ ASSERT(induc->kind_ == InductionVar::kPeriodic);
+ }
+ // Classify first phi and then the rest of the cycle "on-demand".
+ loop->AddInduction(phi, induc);
+ for (intptr_t i = 1, j = p; i < size; i++) {
+ if (++j >= size) j = 0;
+ Classify(loop, scc_[j]);
+ }
+ }
+ }
+}
+
+InductionVar* InductionVarAnalysis::TransferPhi(LoopInfo* loop,
+ Definition* def,
+ intptr_t idx) {
+ InductionVar* induc = nullptr;
+ for (intptr_t i = 0, n = def->InputCount(); i < n; i++) {
+ if (i != idx) {
+ InductionVar* x = Lookup(loop, def->InputAt(i)->definition());
+ if (x == nullptr) {
+ return nullptr;
+ } else if (induc == nullptr) {
+ induc = x;
+ } else if (!induc->IsEqual(x)) {
+ return nullptr;
+ }
+ }
+ }
+ return induc;
+}
+
+InductionVar* InductionVarAnalysis::TransferBinary(LoopInfo* loop,
+ Definition* def) {
+ InductionVar* x = Lookup(loop, def->InputAt(0)->definition());
+ InductionVar* y = Lookup(loop, def->InputAt(1)->definition());
+ switch (def->AsBinaryIntegerOp()->op_kind()) {
+ case Token::kADD:
+ return Add(x, y);
+ case Token::kSUB:
+ return Sub(x, y);
+ case Token::kMUL:
+ return Mul(x, y);
+ default:
+ return nullptr;
+ }
+}
+
+InductionVar* InductionVarAnalysis::TransferUnary(LoopInfo* loop,
+ Definition* def) {
+ InductionVar* x = Lookup(loop, def->InputAt(0)->definition());
+ switch (def->AsUnaryIntegerOp()->op_kind()) {
+ case Token::kNEGATE: {
+ InductionVar* zero = new (zone_) InductionVar(0);
+ return Sub(zero, x);
+ }
+ default:
+ return nullptr;
+ }
+}
+
+InductionVar* InductionVarAnalysis::SolvePhi(LoopInfo* loop,
+ Definition* def,
+ intptr_t idx) {
+ InductionVar* induc = nullptr;
+ for (intptr_t i = 0, n = def->InputCount(); i < n; i++) {
+ if (i != idx) {
+ InductionVar* c = LookupCycle(def->InputAt(i)->definition());
+ if (c == nullptr) {
+ return nullptr;
+ } else if (induc == nullptr) {
+ induc = c;
+ } else if (!induc->IsEqual(c)) {
+ return nullptr;
+ }
+ }
+ }
+ return induc;
+}
+
+InductionVar* InductionVarAnalysis::SolveBinary(LoopInfo* loop,
+ Definition* def,
+ InductionVar* init) {
+ InductionVar* x = Lookup(loop, def->InputAt(0)->definition());
+ InductionVar* y = Lookup(loop, def->InputAt(1)->definition());
+ switch (def->AsBinaryIntegerOp()->op_kind()) {
+ case Token::kADD:
+ if (InductionVar::IsInvariant(x)) {
+ InductionVar* c = LookupCycle(def->InputAt(1)->definition());
+ // The init marker denotes first use, otherwise aggregate.
+ if (c == init) {
+ return x;
+ } else if (InductionVar::IsInvariant(c)) {
+ return Add(x, c);
+ }
+ }
+ if (InductionVar::IsInvariant(y)) {
+ InductionVar* c = LookupCycle(def->InputAt(0)->definition());
+ // The init marker denotes first use, otherwise aggregate.
+ if (c == init) {
+ return y;
+ } else if (InductionVar::IsInvariant(c)) {
+ return Add(c, y);
+ }
+ }
+ break;
+ case Token::kSUB:
+ if (InductionVar::IsInvariant(x)) {
+ InductionVar* c = LookupCycle(def->InputAt(1)->definition());
+ // Note that i = x - i is periodic. The temporary
+ // meaning is expressed in terms of the header phi.
+ if (c == init) {
+ InductionVar* next = Sub(x, init);
+ if (InductionVar::IsInvariant(next)) {
+ return new (zone_)
+ InductionVar(InductionVar::kPeriodic, init, next);
+ }
+ }
+ }
+ if (InductionVar::IsInvariant(y)) {
+ InductionVar* c = LookupCycle(def->InputAt(0)->definition());
+ // The init marker denotes first use, otherwise aggregate.
+ if (c == init) {
+ InductionVar* zero = new (zone_) InductionVar(0);
+ return Sub(zero, y);
+ } else if (InductionVar::IsInvariant(c)) {
+ return Sub(c, y);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ return nullptr;
+}
+
+InductionVar* InductionVarAnalysis::SolveUnary(LoopInfo* loop,
+ Definition* def,
+ InductionVar* init) {
+ switch (def->AsUnaryIntegerOp()->op_kind()) {
+ case Token::kNEGATE: {
+ InductionVar* c = LookupCycle(def->InputAt(0)->definition());
+ // Note that i = - i is periodic. The temporary
+ // meaning is expressed in terms of the header phi.
+ if (c == init) {
+ InductionVar* zero = new (zone_) InductionVar(0);
+ InductionVar* next = Sub(zero, init);
+ if (InductionVar::IsInvariant(next)) {
+ return new (zone_) InductionVar(InductionVar::kPeriodic, init, next);
+ }
+ }
+ return nullptr;
+ }
+ default:
+ return nullptr;
+ }
+}
+
+InductionVar* InductionVarAnalysis::Lookup(LoopInfo* loop, Definition* def) {
+ InductionVar* induc = loop->LookupInduction(def);
+ if (induc == nullptr) {
+ // Loop-invariants are added lazily.
+ if (!loop->Contains(def->GetBlock())) {
+ int64_t val = 0;
+ if (IsConstant(def, &val)) {
+ induc = new (zone_) InductionVar(val);
+ } else {
+ induc = new (zone_) InductionVar(0, 1, def);
+ }
+ loop->AddInduction(def, induc);
+ }
+ }
+ return induc;
+}
+
+InductionVar* InductionVarAnalysis::LookupCycle(Definition* def) {
+ LoopInfo::InductionKV::Pair* pair = cycle_.Lookup(def);
+ if (pair != nullptr) {
+ return pair->value;
+ }
+ return nullptr;
+}
+
+InductionVar* InductionVarAnalysis::Add(InductionVar* x, InductionVar* y) {
+ if (InductionVar::IsInvariant(x)) {
+ if (InductionVar::IsInvariant(y)) {
+ // Invariant + Invariant : only for same or just one instruction.
+ if (x->def_ == y->def_) {
+ return new (zone_)
+ InductionVar(x->offset_ + y->offset_, x->mult_ + y->mult_, x->def_);
+ } else if (y->mult_ == 0) {
+ return new (zone_)
+ InductionVar(x->offset_ + y->offset_, x->mult_, x->def_);
+ } else if (x->mult_ == 0) {
+ return new (zone_)
+ InductionVar(x->offset_ + y->offset_, y->mult_, y->def_);
+ }
+ } else if (y != nullptr) {
+ // Invariant + Induction.
+ InductionVar* i = Add(x, y->initial_);
+ InductionVar* n =
+ y->kind_ == InductionVar::kLinear ? y->next_ : Add(x, y->next_);
+ if (i != nullptr && n != nullptr) {
+ return new (zone_) InductionVar(y->kind_, i, n);
+ }
+ }
+ } else if (InductionVar::IsInvariant(y)) {
+ if (x != nullptr) {
+ // Induction + Invariant.
+ ASSERT(!InductionVar::IsInvariant(x));
+ InductionVar* i = Add(x->initial_, y);
+ InductionVar* n =
+ x->kind_ == InductionVar::kLinear ? x->next_ : Add(x->next_, y);
+ if (i != nullptr && n != nullptr) {
+ return new (zone_) InductionVar(x->kind_, i, n);
+ }
+ }
+ } else if (InductionVar::IsLinear(x) && InductionVar::IsLinear(y)) {
+ // Linear + Linear.
+ InductionVar* i = Add(x->initial_, y->initial_);
+ InductionVar* n = Add(x->next_, y->next_);
+ if (i != nullptr && n != nullptr) {
+ return new (zone_) InductionVar(InductionVar::kLinear, i, n);
+ }
+ }
+ return nullptr;
+}
+
+InductionVar* InductionVarAnalysis::Sub(InductionVar* x, InductionVar* y) {
+ if (InductionVar::IsInvariant(x)) {
+ if (InductionVar::IsInvariant(y)) {
+ // Invariant + Invariant : only for same or just one instruction.
+ if (x->def_ == y->def_) {
+ return new (zone_)
+ InductionVar(x->offset_ - y->offset_, x->mult_ - y->mult_, x->def_);
+ } else if (y->mult_ == 0) {
+ return new (zone_)
+ InductionVar(x->offset_ - y->offset_, x->mult_, x->def_);
+ } else if (x->mult_ == 0) {
+ return new (zone_)
+ InductionVar(x->offset_ - y->offset_, -y->mult_, y->def_);
+ }
+ } else if (y != nullptr) {
+ // Invariant - Induction.
+ InductionVar* i = Sub(x, y->initial_);
+ InductionVar* n;
+ if (y->kind_ == InductionVar::kLinear) {
+ InductionVar* zero = new (zone_) InductionVar(0, 0, nullptr);
+ n = Sub(zero, y->next_);
+ } else {
+ n = Sub(x, y->next_);
+ }
+ if (i != nullptr && n != nullptr) {
+ return new (zone_) InductionVar(y->kind_, i, n);
+ }
+ }
+ } else if (InductionVar::IsInvariant(y)) {
+ if (x != nullptr) {
+ // Induction - Invariant.
+ ASSERT(!InductionVar::IsInvariant(x));
+ InductionVar* i = Sub(x->initial_, y);
+ InductionVar* n =
+ x->kind_ == InductionVar::kLinear ? x->next_ : Sub(x->next_, y);
+ if (i != nullptr && n != nullptr) {
+ return new (zone_) InductionVar(x->kind_, i, n);
+ }
+ }
+ } else if (InductionVar::IsLinear(x) && InductionVar::IsLinear(y)) {
+ // Linear - Linear.
+ InductionVar* i = Sub(x->initial_, y->initial_);
+ InductionVar* n = Sub(x->next_, y->next_);
+ if (i != nullptr && n != nullptr) {
+ return new (zone_) InductionVar(InductionVar::kLinear, i, n);
+ }
+ }
+ return nullptr;
+}
+
+InductionVar* InductionVarAnalysis::Mul(InductionVar* x, InductionVar* y) {
+ // Swap constant left.
+ if (!InductionVar::IsConstant(x)) {
+ InductionVar* tmp = x;
+ x = y;
+ y = tmp;
+ }
+ // Apply constant to any induction.
+ if (InductionVar::IsConstant(x) && y != nullptr) {
+ if (y->kind_ == InductionVar::kInvariant) {
+ return new (zone_)
+ InductionVar(x->offset_ * y->offset_, x->offset_ * y->mult_, y->def_);
+ }
+ return new (zone_)
+ InductionVar(y->kind_, Mul(x, y->initial_), Mul(x, y->next_));
+ }
+ return nullptr;
+}
+
+const char* InductionVar::ToCString() const {
+ char buffer[1024];
+ BufferFormatter f(buffer, sizeof(buffer));
+ switch (kind_) {
+ case kInvariant:
+ if (mult_ != 0) {
+ f.Print("(%" Pd64 " + %" Pd64 " x %.4s)", offset_, mult_,
+ def_->ToCString());
+ } else {
+ f.Print("%" Pd64, offset_);
+ }
+ break;
+ case kLinear:
+ f.Print("LIN(%s + %s * i)", initial_->ToCString(), next_->ToCString());
+ break;
+ case kWrapAround:
+ f.Print("WRAP(%s, %s)", initial_->ToCString(), next_->ToCString());
+ break;
+ case kPeriodic:
+ f.Print("PERIOD(%s, %s)", initial_->ToCString(), next_->ToCString());
+ break;
+ }
+ return Thread::Current()->zone()->MakeCopyOfString(buffer);
+}
+
LoopInfo::LoopInfo(intptr_t id, BlockEntryInstr* header, BitVector* blocks)
: id_(id),
header_(header),
blocks_(blocks),
back_edges_(),
+ induction_(),
outer_(nullptr),
inner_(nullptr),
next_(nullptr) {}
@@ -37,13 +650,22 @@
return false;
}
-bool LoopInfo::IsIn(LoopInfo* other) const {
- if (other != nullptr) {
- return other->blocks_->Contains(header_->preorder_number());
+bool LoopInfo::IsHeaderPhi(Instruction* instr) const {
+ return instr->IsPhi() && instr->GetBlock() == header_ &&
+ !instr->AsPhi()->IsRedundant(); // phi(x,..,x) = x
+}
+
+bool LoopInfo::IsIn(LoopInfo* loop) const {
+ if (loop != nullptr) {
+ return loop->Contains(header_);
}
return false;
}
+bool LoopInfo::Contains(BlockEntryInstr* block) const {
+ return blocks_->Contains(block->preorder_number());
+}
+
intptr_t LoopInfo::NestingDepth() const {
intptr_t nesting_depth = 1;
for (LoopInfo* o = outer_; o != nullptr; o = o->outer()) {
@@ -52,6 +674,22 @@
return nesting_depth;
}
+void LoopInfo::ResetInduction() {
+ induction_.Clear();
+}
+
+void LoopInfo::AddInduction(Definition* def, InductionVar* induc) {
+ induction_.Insert(InductionKV::Pair(def, induc));
+}
+
+InductionVar* LoopInfo::LookupInduction(Definition* def) const {
+ InductionKV::Pair* pair = induction_.Lookup(def);
+ if (pair != nullptr) {
+ return pair->value;
+ }
+ return nullptr;
+}
+
const char* LoopInfo::ToCString() const {
char buffer[1024];
BufferFormatter f(buffer, sizeof(buffer));
@@ -83,7 +721,7 @@
// Link every entry block to the closest enveloping loop.
for (intptr_t i = 0, n = headers_->length(); i < n; ++i) {
LoopInfo* loop = (*headers_)[i]->loop_info();
- for (BitVector::Iterator it(loop->blocks()); !it.Done(); it.Advance()) {
+ for (BitVector::Iterator it(loop->blocks_); !it.Done(); it.Advance()) {
BlockEntryInstr* block = preorder_[it.Current()];
if (block->loop_info() == nullptr) {
block->set_loop_info(loop);
@@ -110,14 +748,14 @@
}
// If tracing is requested, print the loop hierarchy.
if (FLAG_trace_optimization) {
- Print(top());
+ Print(top_);
}
}
void LoopHierarchy::Print(LoopInfo* loop) {
for (; loop != nullptr; loop = loop->next_) {
THR_Print("%s {", loop->ToCString());
- for (BitVector::Iterator it(loop->blocks()); !it.Done(); it.Advance()) {
+ for (BitVector::Iterator it(loop->blocks_); !it.Done(); it.Advance()) {
THR_Print(" B%" Pd, preorder_[it.Current()]->block_id());
}
THR_Print(" }\n");
@@ -125,6 +763,10 @@
}
}
+void LoopHierarchy::ComputeInduction() const {
+ InductionVarAnalysis(preorder_).VisitHierarchy(top_);
+}
+
} // namespace dart
#endif // !defined(DART_PRECOMPILED_RUNTIME)
diff --git a/runtime/vm/compiler/backend/loops.h b/runtime/vm/compiler/backend/loops.h
index 555f69f..1d5b45a 100644
--- a/runtime/vm/compiler/backend/loops.h
+++ b/runtime/vm/compiler/backend/loops.h
@@ -10,6 +10,116 @@
namespace dart {
+// Information on an induction variable in a particular loop.
+//
+// Invariant:
+// offset + mult * def
+// Linear:
+// initial + next * i, for invariant initial and next,
+// and a "normalized" loop index i
+// Wrap-around:
+// initial then next, for invariant initial and any next
+// Periodic:
+// alternate initial and next, for invariant initial and next
+//
+class InductionVar : public ZoneAllocated {
+ public:
+ enum Kind {
+ kInvariant,
+ kLinear,
+ kWrapAround,
+ kPeriodic,
+ };
+
+ // Constructor for an invariant.
+ InductionVar(int64_t offset, int64_t mult, Definition* def)
+ : kind_(kInvariant), offset_(offset), mult_(mult), def_(def) {}
+
+ // Constructor for a constant.
+ explicit InductionVar(int64_t offset) : InductionVar(offset, 0, nullptr) {}
+
+ // Constructor for induction.
+ InductionVar(Kind kind, InductionVar* initial, InductionVar* next)
+ : kind_(kind), initial_(initial), next_(next) {
+ ASSERT(IsInvariant(initial));
+ switch (kind) {
+ case kLinear:
+ case kPeriodic:
+ ASSERT(IsInvariant(next));
+ break;
+ case kWrapAround:
+ ASSERT(next != nullptr);
+ break;
+ default:
+ UNREACHABLE();
+ }
+ }
+
+ // Returns true if the other induction is structually equivalent.
+ bool IsEqual(InductionVar* other) const {
+ ASSERT(other != nullptr);
+ if (kind_ == other->kind_) {
+ switch (kind_) {
+ case kInvariant:
+ return offset_ == other->offset_ && mult_ == other->mult_ &&
+ (mult_ == 0 || def_ == other->def_);
+ case kLinear:
+ case kWrapAround:
+ case kPeriodic:
+ return initial_->IsEqual(other->initial_) &&
+ next_->IsEqual(other->next_);
+ }
+ }
+ return false;
+ }
+
+ // For debugging.
+ const char* ToCString() const;
+
+ // Returns true if x is invariant.
+ static bool IsInvariant(InductionVar* x) {
+ return x != nullptr && x->kind_ == kInvariant;
+ }
+
+ // Returns true if x is a constant (and invariant).
+ static bool IsConstant(InductionVar* x) {
+ return x != nullptr && x->kind_ == kInvariant && x->mult_ == 0;
+ }
+
+ // Returns true if x is linear.
+ static bool IsLinear(InductionVar* x) {
+ return x != nullptr && x->kind_ == kLinear;
+ }
+
+ // Returns true if x is wrap-around.
+ static bool IsWrapAround(InductionVar* x) {
+ return x != nullptr && x->kind_ == kWrapAround;
+ }
+
+ // Returns true if x is periodic.
+ static bool IsPeriodic(InductionVar* x) {
+ return x != nullptr && x->kind_ == kPeriodic;
+ }
+
+ private:
+ friend class InductionVarAnalysis;
+
+ const Kind kind_;
+ union {
+ struct {
+ int64_t offset_;
+ int64_t mult_;
+ Definition* def_;
+ };
+ struct {
+ InductionVar* initial_;
+ InductionVar* next_;
+ };
+ };
+
+ DISALLOW_COPY_AND_ASSIGN(InductionVar);
+};
+
// Information on a "natural loop" in the flow graph.
class LoopInfo : public ZoneAllocated {
public:
@@ -24,12 +134,27 @@
// Returns true if given block is backedge of this loop.
bool IsBackEdge(BlockEntryInstr* block) const;
- // Returns true if this loop is nested inside other loop.
- bool IsIn(LoopInfo* other) const;
+ // Returns true if given instruction is a header phi for this loop.
+ bool IsHeaderPhi(Instruction* instr) const;
+
+ // Returns true if this loop is nested inside given loop.
+ bool IsIn(LoopInfo* loop) const;
+
+ // Returns true if this loop contains given block.
+ bool Contains(BlockEntryInstr* block) const;
// Returns the nesting depth of this loop.
intptr_t NestingDepth() const;
+ // Resets induction.
+ void ResetInduction();
+
+ // Assigns induction to a definition.
+ void AddInduction(Definition* def, InductionVar* induc);
+
+ // Looks up induction.
+ InductionVar* LookupInduction(Definition* def) const;
+
// Getters.
intptr_t id() const { return id_; }
BlockEntryInstr* header() const { return header_; }
@@ -43,8 +168,11 @@
const char* ToCString() const;
private:
+ friend class InductionVarAnalysis;
friend class LoopHierarchy;
+ typedef RawPointerKeyValueTrait<Definition, InductionVar*> InductionKV;
+
// Unique id of loop. We use its index in the
// loop header array for this.
const intptr_t id_;
@@ -59,6 +187,9 @@
// Back edges of loop (usually one).
GrowableArray<BlockEntryInstr*> back_edges_;
+ // Map instruction -> induction for this loop.
+ DirectChainedHashMap<InductionKV> induction_;
+
// Loop hierarchy.
LoopInfo* outer_;
LoopInfo* inner_;
@@ -82,6 +213,9 @@
// Returns total number of loops in the hierarchy.
intptr_t num_loops() const { return headers_->length(); }
+ // Performs induction variable analysis on all loops.
+ void ComputeInduction() const;
+
private:
void Build();
void Print(LoopInfo* loop);
diff --git a/runtime/vm/compiler/backend/loops_test.cc b/runtime/vm/compiler/backend/loops_test.cc
new file mode 100644
index 0000000..8b27faa
--- /dev/null
+++ b/runtime/vm/compiler/backend/loops_test.cc
@@ -0,0 +1,323 @@
+// Copyright (c) 2018, 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.
+//
+// Unit tests specific to loops and induction variables.
+// Note, try to avoid relying on information that is subject
+// to change (block ids, variable numbers, etc.) in order
+// to make this test less sensitive to unrelated changes.
+
+#include "vm/compiler/backend/loops.h"
+#include "vm/compiler/backend/il_printer.h"
+#include "vm/compiler/backend/inliner.h"
+#include "vm/compiler/backend/type_propagator.h"
+#include "vm/compiler/compiler_pass.h"
+#include "vm/compiler/frontend/kernel_to_il.h"
+#include "vm/compiler/jit/jit_call_specializer.h"
+#include "vm/log.h"
+#include "vm/object.h"
+#include "vm/parser.h"
+#include "vm/symbols.h"
+#include "vm/unit_test.h"
+
+namespace dart {
+
+// Helper method to construct an induction debug string for loop hierarchy.
+void TestString(BufferFormatter* f,
+ LoopInfo* loop,
+ const GrowableArray<BlockEntryInstr*>& preorder) {
+ for (; loop != nullptr; loop = loop->next()) {
+ intptr_t depth = loop->NestingDepth();
+ f->Print("%*c[%" Pd "\n", static_cast<int>(2 * depth), ' ', loop->id());
+ for (BitVector::Iterator block_it(loop->blocks()); !block_it.Done();
+ block_it.Advance()) {
+ BlockEntryInstr* block = preorder[block_it.Current()];
+ if (block->IsJoinEntry()) {
+ for (PhiIterator it(block->AsJoinEntry()); !it.Done(); it.Advance()) {
+ InductionVar* induc = loop->LookupInduction(it.Current());
+ if (induc != nullptr) {
+ f->Print("%*c%s\n", static_cast<int>(2 * depth), ' ',
+ induc->ToCString());
+ }
+ }
+ }
+ for (ForwardInstructionIterator it(block); !it.Done(); it.Advance()) {
+ InductionVar* induc =
+ loop->LookupInduction(it.Current()->AsDefinition());
+ if (induc != nullptr) {
+ f->Print("%*c%s\n", static_cast<int>(2 * depth), ' ',
+ induc->ToCString());
+ }
+ }
+ }
+ TestString(f, loop->inner(), preorder);
+ f->Print("%*c]\n", static_cast<int>(2 * depth), ' ');
+ }
+}
+
+// Helper method to build CFG and compute induction.
+static const char* ComputeInduction(Thread* thread, const char* script_chars) {
+ // Invoke the script.
+ Dart_Handle script = TestCase::LoadTestScript(script_chars, NULL);
+ Dart_Handle result = Dart_Invoke(script, NewString("main"), 0, NULL);
+ EXPECT_VALID(result);
+
+ // Find parsed function "foo".
+ TransitionNativeToVM transition(thread);
+ Zone* zone = thread->zone();
+ Library& lib =
+ Library::ZoneHandle(Library::RawCast(Api::UnwrapHandle(script)));
+ RawFunction* raw_func =
+ lib.LookupLocalFunction(String::Handle(Symbols::New(thread, "foo")));
+ ParsedFunction* parsed_function =
+ new (zone) ParsedFunction(thread, Function::ZoneHandle(zone, raw_func));
+ EXPECT(parsed_function != nullptr);
+
+ // Build flow graph.
+ CompilerState state(thread);
+ ZoneGrowableArray<const ICData*>* ic_data_array =
+ new (zone) ZoneGrowableArray<const ICData*>();
+ parsed_function->function().RestoreICDataMap(ic_data_array, true);
+ kernel::FlowGraphBuilder builder(parsed_function, ic_data_array, nullptr,
+ nullptr, true, DeoptId::kNone);
+ FlowGraph* flow_graph = builder.BuildGraph();
+ EXPECT(flow_graph != nullptr);
+
+ // Setup some pass data structures and perform minimum passes.
+ SpeculativeInliningPolicy speculative_policy(/*enable_blacklist*/ false);
+ CompilerPassState pass_state(thread, flow_graph, &speculative_policy);
+ JitCallSpecializer call_specializer(flow_graph, &speculative_policy);
+ pass_state.call_specializer = &call_specializer;
+ flow_graph->ComputeSSA(0, nullptr);
+ FlowGraphTypePropagator::Propagate(flow_graph);
+ call_specializer.ApplyICData();
+
+ // Build loop hierarchy and find induction.
+ const LoopHierarchy& hierarchy = flow_graph->GetLoopHierarchy();
+ hierarchy.ComputeInduction();
+ flow_graph->RemoveRedefinitions(); // don't query later
+
+ // Construct and return a debug string for testing.
+ char buffer[1024];
+ BufferFormatter f(buffer, sizeof(buffer));
+ TestString(&f, hierarchy.top(), flow_graph->preorder());
+ return Thread::Current()->zone()->MakeCopyOfString(buffer);
+}
+
+TEST_CASE(BasicInduction) {
+ const char* script_chars =
+ "foo() {\n"
+ " for (int i = 0; i < 100; i++) {\n"
+ " }\n"
+ "}\n"
+ "main() {\n"
+ " foo();\n"
+ "}\n";
+ const char* expected =
+ " [0\n"
+ " LIN(0 + 1 * i)\n" // phi
+ " LIN(1 + 1 * i)\n" // add
+ " ]\n";
+ EXPECT_STREQ(expected, ComputeInduction(thread, script_chars));
+}
+
+TEST_CASE(BasicInductionStepUp) {
+ const char* script_chars =
+ "foo() {\n"
+ " for (int i = 10; i < 100; i += 2) {\n"
+ " }\n"
+ "}\n"
+ "main() {\n"
+ " foo();\n"
+ "}\n";
+ const char* expected =
+ " [0\n"
+ " LIN(10 + 2 * i)\n" // phi
+ " LIN(12 + 2 * i)\n" // add
+ " ]\n";
+ EXPECT_STREQ(expected, ComputeInduction(thread, script_chars));
+}
+
+TEST_CASE(BasicInductionStepDown) {
+ const char* script_chars =
+ "foo() {\n"
+ " for (int i = 100; i >= 0; i -= 7) {\n"
+ " }\n"
+ "}\n"
+ "main() {\n"
+ " foo();\n"
+ "}\n";
+ const char* expected =
+ " [0\n"
+ " LIN(100 + -7 * i)\n" // phi
+ " LIN(93 + -7 * i)\n" // add
+ " ]\n";
+ EXPECT_STREQ(expected, ComputeInduction(thread, script_chars));
+}
+
+TEST_CASE(BasicInductionLoopNest) {
+ const char* script_chars =
+ "foo() {\n"
+ " for (int i = 0; i < 100; i++) {\n"
+ " for (int j = 1; j < 100; j++) {\n"
+ " for (int k = 2; k < 100; k++) {\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ "}\n"
+ "main() {\n"
+ " foo();\n"
+ "}\n";
+ const char* expected =
+ " [2\n"
+ " LIN(0 + 1 * i)\n" // i
+ " LIN(1 + 1 * i)\n"
+ " [1\n"
+ " LIN(1 + 1 * i)\n" // j
+ " LIN(2 + 1 * i)\n"
+ " [0\n"
+ " LIN(2 + 1 * i)\n" // k
+ " LIN(3 + 1 * i)\n"
+ " ]\n"
+ " ]\n"
+ " ]\n";
+ EXPECT_STREQ(expected, ComputeInduction(thread, script_chars));
+}
+
+TEST_CASE(ChainInduction) {
+ const char* script_chars =
+ "foo() {\n"
+ " int j = 1;\n"
+ " for (int i = 0; i < 100; i++) {\n"
+ " j += 5;\n"
+ " j += 7;\n"
+ " }\n"
+ "}\n"
+ "main() {\n"
+ " foo();\n"
+ "}\n";
+ const char* expected =
+ " [0\n"
+ " LIN(1 + 12 * i)\n" // phi (j)
+ " LIN(0 + 1 * i)\n" // phi
+ " LIN(6 + 12 * i)\n" // j-add
+ " LIN(13 + 12 * i)\n" // j-add
+ " LIN(1 + 1 * i)\n" // add
+ " ]\n";
+ EXPECT_STREQ(expected, ComputeInduction(thread, script_chars));
+}
+
+TEST_CASE(TwoWayInduction) {
+ const char* script_chars =
+ "foo() {\n"
+ " int j = 123;\n"
+ " for (int i = 0; i < 100; i++) {\n"
+ " if (i == 10) {\n"
+ " j += 3;\n"
+ " } else {\n"
+ " j += 3;\n"
+ " }\n"
+ " }\n"
+ "}\n"
+ "main() {\n"
+ " foo();\n"
+ "}\n";
+ const char* expected =
+ " [0\n"
+ " LIN(123 + 3 * i)\n" // phi (j)
+ " LIN(0 + 1 * i)\n" // phi
+ " LIN(126 + 3 * i)\n" // j-true
+ " LIN(126 + 3 * i)\n" // j-false
+ " LIN(1 + 1 * i)\n" // add
+ " LIN(126 + 3 * i)\n" // phi (j)
+ " ]\n";
+ EXPECT_STREQ(expected, ComputeInduction(thread, script_chars));
+}
+
+TEST_CASE(DerivedInduction) {
+ const char* script_chars =
+ "foo() {\n"
+ " for (int i = 1; i < 100; i++) {\n"
+ " int a = i + 3;\n"
+ " int b = i - 5;\n"
+ " int c = i * 7;\n"
+ " int d = - i;\n"
+ " }\n"
+ "}\n"
+ "main() {\n"
+ " foo();\n"
+ "}\n";
+ const char* expected =
+ " [0\n"
+ " LIN(1 + 1 * i)\n" // phi
+ " LIN(4 + 1 * i)\n" // a
+ " LIN(-4 + 1 * i)\n" // b
+ " LIN(7 + 7 * i)\n" // c
+ " LIN(-1 + -1 * i)\n" // d
+ " LIN(2 + 1 * i)\n" // add
+ " ]\n";
+ EXPECT_STREQ(expected, ComputeInduction(thread, script_chars));
+}
+
+TEST_CASE(WrapAroundAndDerived) {
+ const char* script_chars =
+ "foo() {\n"
+ " int w = 99;\n"
+ " for (int i = 0; i < 100; i++) {\n"
+ " int a = w + 3;\n"
+ " int b = w - 5;\n"
+ " int c = w * 7;\n"
+ " int d = - w;\n"
+ " w = i;\n"
+ " }\n"
+ "}\n"
+ "main() {\n"
+ " foo();\n"
+ "}\n";
+ const char* expected =
+ " [0\n"
+ " WRAP(99, LIN(0 + 1 * i))\n" // phi (w)
+ " LIN(0 + 1 * i)\n" // phi
+ " WRAP(102, LIN(3 + 1 * i))\n" // a
+ " WRAP(94, LIN(-5 + 1 * i))\n" // b
+ " WRAP(693, LIN(0 + 7 * i))\n" // c
+ " WRAP(-99, LIN(0 + -1 * i))\n" // d
+ " LIN(1 + 1 * i)\n" // add
+ " ]\n";
+ EXPECT_STREQ(ComputeInduction(thread, script_chars), expected);
+}
+
+TEST_CASE(PeriodicAndDerived) {
+ const char* script_chars =
+ "foo() {\n"
+ " int p1 = 3;\n"
+ " int p2 = 5;\n"
+ " for (int i = 0; i < 100; i++) {\n"
+ " int a = p1 + 3;\n"
+ " int b = p1 - 5;\n"
+ " int c = p1 * 7;\n"
+ " int d = - p1;\n"
+ " p1 = - p1;\n"
+ " p2 = 100 - p2;\n"
+ " }\n"
+ "}\n"
+ "main() {\n"
+ " foo();\n"
+ "}\n";
+ const char* expected =
+ " [0\n"
+ " PERIOD(3, -3)\n" // phi(p1)
+ " PERIOD(5, 95)\n" // phi(p2)
+ " LIN(0 + 1 * i)\n" // phi
+ " PERIOD(6, 0)\n" // a
+ " PERIOD(-2, -8)\n" // b
+ " PERIOD(21, -21)\n" // c
+ " PERIOD(-3, 3)\n" // d
+ " PERIOD(-3, 3)\n" // p1
+ " PERIOD(95, 5)\n" // p2
+ " LIN(1 + 1 * i)\n" // add
+ " ]\n";
+ EXPECT_STREQ(ComputeInduction(thread, script_chars), expected);
+}
+
+} // namespace dart
diff --git a/runtime/vm/compiler/compiler_sources.gni b/runtime/vm/compiler/compiler_sources.gni
index c771039..54fc581 100644
--- a/runtime/vm/compiler/compiler_sources.gni
+++ b/runtime/vm/compiler/compiler_sources.gni
@@ -125,6 +125,7 @@
"assembler/disassembler_test.cc",
"backend/il_test.cc",
"backend/locations_helpers_test.cc",
+ "backend/loops_test.cc",
"backend/range_analysis_test.cc",
"cha_test.cc",
]