[vm/compiler] Replace simple induction analysis with new framework

Rationale:
Replacing the simple induction analysis in the range analyzer
with the new framework for finding induction variables removes
a lot of code and even saves some memory in the IR (for phis).
Note that this is only the first phase, i.e. using the new
framework for the same behavior in the range analyzer. In the
second phase, the new framework will be fully exploited to
compute more accurate ranges in many more cases.
Change-Id: Iae5bd5a155ef8592558b5bdb2388c5aa9da64e59
Reviewed-on: https://dart-review.googlesource.com/c/84500
Reviewed-by: Vyacheslav Egorov <vegorov@google.com>
Commit-Queue: Aart Bik <ajcbik@google.com>
This commit is contained in:
Aart Bik 2018-11-26 19:05:01 +00:00 committed by commit-bot@chromium.org
parent 9903d86d50
commit 01e85fa2e0
5 changed files with 166 additions and 238 deletions

View file

@ -1977,8 +1977,6 @@ class TemplateDefinition : public CSETrait<Definition, PureDefinition>::Base {
virtual void RawSetInputAt(intptr_t i, Value* value) { inputs_[i] = value; }
};
class InductionVariableInfo;
class PhiInstr : public Definition {
public:
PhiInstr(JoinEntryInstr* block, intptr_t num_inputs)
@ -1986,7 +1984,6 @@ class PhiInstr : public Definition {
inputs_(num_inputs),
representation_(kTagged),
reaching_defs_(NULL),
loop_variable_info_(NULL),
is_alive_(false),
is_receiver_(kUnknownReceiver) {
for (intptr_t i = 0; i < num_inputs; ++i) {
@ -2042,14 +2039,6 @@ class PhiInstr : public Definition {
// A phi is redundant if all input operands are the same.
bool IsRedundant() const;
void set_induction_variable_info(InductionVariableInfo* info) {
loop_variable_info_ = info;
}
InductionVariableInfo* induction_variable_info() {
return loop_variable_info_;
}
PRINT_TO_SUPPORT
enum ReceiverType { kUnknownReceiver = -1, kNotReceiver = 0, kReceiver = 1 };
@ -2071,7 +2060,6 @@ class PhiInstr : public Definition {
GrowableArray<Value*> inputs_;
Representation representation_;
BitVector* reaching_defs_;
InductionVariableInfo* loop_variable_info_;
bool is_alive_;
int8_t is_receiver_;

View file

@ -65,16 +65,19 @@ class InductionVarAnalysis : public ValueObject {
void Classify(LoopInfo* loop, Definition* def);
void ClassifySCC(LoopInfo* loop);
// Transfer methods. Computes how induction of the operands, if any,
// Transfer methods. Compute 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
// Solver methods. Compute 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* SolveConstraint(LoopInfo* loop,
Definition* def,
InductionVar* init);
InductionVar* SolveBinary(LoopInfo* loop,
Definition* def,
InductionVar* init);
@ -239,8 +242,8 @@ void InductionVarAnalysis::Classify(LoopInfo* loop, Definition* def) {
}
} else if (def->IsPhi()) {
induc = TransferPhi(loop, def);
} else if (def->IsRedefinition() || def->IsConstraint() || def->IsBox() ||
def->IsUnbox()) {
} else if (def->IsRedefinition() || def->IsBox() || def->IsUnbox() ||
def->IsConstraint()) {
induc = Lookup(loop, def->InputAt(0)->definition()); // pass-through
} else if (def->IsBinaryIntegerOp()) {
induc = TransferBinary(loop, def);
@ -278,9 +281,10 @@ void InductionVarAnalysis::ClassifySCC(LoopInfo* loop) {
InductionVar* update = nullptr;
if (def->IsPhi()) {
update = SolvePhi(loop, def);
} else if (def->IsRedefinition() || def->IsConstraint() || def->IsBox() ||
def->IsUnbox()) {
} else if (def->IsRedefinition() || def->IsBox() || def->IsUnbox()) {
update = LookupCycle(def->InputAt(0)->definition()); // pass-through
} else if (def->IsConstraint()) {
update = SolveConstraint(loop, def, init);
} else if (def->IsBinaryIntegerOp()) {
update = SolveBinary(loop, def, init);
} else if (def->IsUnaryIntegerOp()) {
@ -381,6 +385,21 @@ InductionVar* InductionVarAnalysis::SolvePhi(LoopInfo* loop,
return induc;
}
InductionVar* InductionVarAnalysis::SolveConstraint(LoopInfo* loop,
Definition* def,
InductionVar* init) {
InductionVar* c = LookupCycle(def->InputAt(0)->definition());
if (c == init) {
// Record a non-artifical bound constraint on a phi.
// TODO(ajcbik): detect full loop logic, trip counts, etc.
ConstraintInstr* constraint = def->AsConstraint();
if (constraint->target() != nullptr) {
loop->limit_ = constraint;
}
}
return c;
}
InductionVar* InductionVarAnalysis::SolveBinary(LoopInfo* loop,
Definition* def,
InductionVar* init) {
@ -629,6 +648,7 @@ LoopInfo::LoopInfo(intptr_t id, BlockEntryInstr* header, BitVector* blocks)
blocks_(blocks),
back_edges_(),
induction_(),
limit_(nullptr),
outer_(nullptr),
inner_(nullptr),
next_(nullptr) {}
@ -650,9 +670,9 @@ bool LoopInfo::IsBackEdge(BlockEntryInstr* block) const {
return false;
}
bool LoopInfo::IsHeaderPhi(Instruction* instr) const {
return instr->IsPhi() && instr->GetBlock() == header_ &&
!instr->AsPhi()->IsRedundant(); // phi(x,..,x) = x
bool LoopInfo::IsHeaderPhi(Definition* def) const {
return def != nullptr && def->IsPhi() && def->GetBlock() == header_ &&
!def->AsPhi()->IsRedundant(); // phi(x,..,x) = x
}
bool LoopInfo::IsIn(LoopInfo* loop) const {
@ -679,6 +699,8 @@ void LoopInfo::ResetInduction() {
}
void LoopInfo::AddInduction(Definition* def, InductionVar* induc) {
ASSERT(def != nullptr);
ASSERT(induc != nullptr);
induction_.Insert(InductionKV::Pair(def, induc));
}

View file

@ -38,7 +38,7 @@ class InductionVar : public ZoneAllocated {
// Constructor for a constant.
explicit InductionVar(int64_t offset) : InductionVar(offset, 0, nullptr) {}
// Constructor for induction.
// Constructor for an induction.
InductionVar(Kind kind, InductionVar* initial, InductionVar* next)
: kind_(kind), initial_(initial), next_(next) {
ASSERT(IsInvariant(initial));
@ -73,6 +73,29 @@ class InductionVar : public ZoneAllocated {
return false;
}
// Getters.
Kind kind() const { return kind_; }
int64_t offset() const {
ASSERT(kind_ == kInvariant);
return offset_;
}
int64_t mult() const {
ASSERT(kind_ == kInvariant);
return mult_;
}
Definition* def() const {
ASSERT(kind_ == kInvariant);
return def_;
}
InductionVar* initial() const {
ASSERT(kind_ != kInvariant);
return initial_;
}
InductionVar* next() const {
ASSERT(kind_ != kInvariant);
return next_;
}
// For debugging.
const char* ToCString() const;
@ -134,8 +157,8 @@ class LoopInfo : public ZoneAllocated {
// Returns true if given block is backedge of this loop.
bool IsBackEdge(BlockEntryInstr* block) const;
// Returns true if given instruction is a header phi for this loop.
bool IsHeaderPhi(Instruction* instr) const;
// Returns true if given definition is a header phi for this loop.
bool IsHeaderPhi(Definition* def) const;
// Returns true if this loop is nested inside given loop.
bool IsIn(LoopInfo* loop) const;
@ -158,8 +181,9 @@ class LoopInfo : public ZoneAllocated {
// Getters.
intptr_t id() const { return id_; }
BlockEntryInstr* header() const { return header_; }
const GrowableArray<BlockEntryInstr*>& back_edges() { return back_edges_; }
BitVector* blocks() const { return blocks_; }
const GrowableArray<BlockEntryInstr*>& back_edges() { return back_edges_; }
ConstraintInstr* limit() const { return limit_; }
LoopInfo* outer() const { return outer_; }
LoopInfo* inner() const { return inner_; }
LoopInfo* next() const { return next_; }
@ -187,9 +211,12 @@ class LoopInfo : public ZoneAllocated {
// Back edges of loop (usually one).
GrowableArray<BlockEntryInstr*> back_edges_;
// Map instruction -> induction for this loop.
// Map definition -> induction for this loop.
DirectChainedHashMap<InductionKV> induction_;
// Constraint on a header phi.
ConstraintInstr* limit_;
// Loop hierarchy.
LoopInfo* outer_;
LoopInfo* inner_;

View file

@ -30,7 +30,7 @@ DECLARE_FLAG(bool, trace_constant_propagation);
void RangeAnalysis::Analyze() {
CollectValues();
InsertConstraints();
DiscoverSimpleInductionVariables();
flow_graph_->GetLoopHierarchy().ComputeInduction();
InferRanges();
EliminateRedundantBoundsChecks();
MarkUnreachableBlocks();
@ -43,6 +43,7 @@ void RangeAnalysis::Analyze() {
RemoveConstraints();
}
// Helper method to chase to a constrained definition.
static Definition* UnwrapConstraint(Definition* defn) {
while (defn->IsConstraint()) {
defn = defn->AsConstraint()->value()->definition();
@ -50,167 +51,6 @@ static Definition* UnwrapConstraint(Definition* defn) {
return defn;
}
// Simple induction variable is a variable that satisfies the following pattern:
//
// v1 <- phi(v0, v1 + 1)
//
// If there are two simple induction variables in the same block and one of
// them is constrained - then another one is constrained as well, e.g.
// from
//
// B1:
// v3 <- phi(v0, v3 + 1)
// v4 <- phi(v2, v4 + 1)
// Bx:
// v3 is constrained to [v0, v1]
//
// it follows that
//
// Bx:
// v4 is constrained to [v2, v2 + (v0 - v1)]
//
// This pass essentially pattern matches induction variables introduced
// like this:
//
// for (var i = i0, j = j0; i < L; i++, j++) {
// j is known to be within [j0, j0 + (L - i0 - 1)]
// }
//
class InductionVariableInfo : public ZoneAllocated {
public:
InductionVariableInfo(PhiInstr* phi,
Definition* initial_value,
BinarySmiOpInstr* increment,
ConstraintInstr* limit)
: phi_(phi),
initial_value_(initial_value),
increment_(increment),
limit_(limit),
bound_(NULL) {}
PhiInstr* phi() const { return phi_; }
Definition* initial_value() const { return initial_value_; }
BinarySmiOpInstr* increment() const { return increment_; }
// Outermost constraint that constrains this induction variable into
// [-inf, X] range.
ConstraintInstr* limit() const { return limit_; }
// Induction variable from the same join block that has limiting constraint.
PhiInstr* bound() const { return bound_; }
void set_bound(PhiInstr* bound) { bound_ = bound; }
private:
PhiInstr* phi_;
Definition* initial_value_;
BinarySmiOpInstr* increment_;
ConstraintInstr* limit_;
PhiInstr* bound_;
};
static ConstraintInstr* FindBoundingConstraint(PhiInstr* phi,
Definition* defn) {
ConstraintInstr* limit = NULL;
for (ConstraintInstr* constraint = defn->AsConstraint(); constraint != NULL;
constraint = constraint->value()->definition()->AsConstraint()) {
if (constraint->target() == NULL) {
continue; // Only interested in non-artifical constraints.
}
Range* constraining_range = constraint->constraint();
if (constraining_range->min().Equals(RangeBoundary::MinSmi()) &&
(constraining_range->max().IsSymbol() &&
phi->IsDominatedBy(constraining_range->max().symbol()))) {
limit = constraint;
}
}
return limit;
}
static InductionVariableInfo* DetectSimpleInductionVariable(PhiInstr* phi) {
if (phi->Type()->ToCid() != kSmiCid) {
return NULL;
}
if (phi->InputCount() != 2) {
return NULL;
}
BitVector* loop_blocks = phi->block()->loop_info()->blocks();
const intptr_t backedge_idx =
loop_blocks->Contains(phi->block()->PredecessorAt(0)->preorder_number())
? 0
: 1;
Definition* initial_value = phi->InputAt(1 - backedge_idx)->definition();
BinarySmiOpInstr* increment =
UnwrapConstraint(phi->InputAt(backedge_idx)->definition())
->AsBinarySmiOp();
if ((increment != NULL) && (increment->op_kind() == Token::kADD) &&
(UnwrapConstraint(increment->left()->definition()) == phi) &&
increment->right()->BindsToConstant() &&
increment->right()->BoundConstant().IsSmi() &&
(Smi::Cast(increment->right()->BoundConstant()).Value() == 1)) {
return new InductionVariableInfo(
phi, initial_value, increment,
FindBoundingConstraint(phi, increment->left()->definition()));
}
return NULL;
}
// TODO(ajcbik): move induction variable recognition in loop framework
void RangeAnalysis::DiscoverSimpleInductionVariables() {
GrowableArray<InductionVariableInfo*> loop_variables;
for (BlockIterator block_it = flow_graph_->reverse_postorder_iterator();
!block_it.Done(); block_it.Advance()) {
BlockEntryInstr* block = block_it.Current();
JoinEntryInstr* join = block->AsJoinEntry();
if (join != NULL && join->loop_info() != NULL &&
join->loop_info()->header() == join) {
loop_variables.Clear();
for (PhiIterator phi_it(join); !phi_it.Done(); phi_it.Advance()) {
PhiInstr* current = phi_it.Current();
InductionVariableInfo* info = DetectSimpleInductionVariable(current);
if (info != NULL) {
if (FLAG_support_il_printer && FLAG_trace_range_analysis) {
THR_Print("Simple loop variable: %s bound <%s>\n",
current->ToCString(),
info->limit() != NULL ? info->limit()->ToCString() : "?");
}
loop_variables.Add(info);
}
}
}
InductionVariableInfo* bound = NULL;
for (intptr_t i = 0; i < loop_variables.length(); i++) {
if (loop_variables[i]->limit() != NULL) {
bound = loop_variables[i];
break;
}
}
if (bound != NULL) {
for (intptr_t i = 0; i < loop_variables.length(); i++) {
InductionVariableInfo* info = loop_variables[i];
info->set_bound(bound->phi());
info->phi()->set_induction_variable_info(info);
}
}
}
}
void RangeAnalysis::CollectValues() {
auto graph_entry = flow_graph_->graph_entry();
@ -1077,6 +917,8 @@ class BoundsCheckGeneralizer {
}
typedef Definition* (BoundsCheckGeneralizer::*PhiBoundFunc)(PhiInstr*,
LoopInfo*,
InductionVar*,
Instruction*);
// Construct symbolic lower bound for a value at the given point.
@ -1091,6 +933,52 @@ class BoundsCheckGeneralizer {
value, point);
}
// Helper methods to implement "older" business logic.
// TODO(ajcbik): generalize with new induction variable information
// Only accept loops with a smi constraint on smi induction.
LoopInfo* GetSmiBoundedLoop(PhiInstr* phi) {
LoopInfo* loop = phi->GetBlock()->loop_info();
if (loop == nullptr) {
return nullptr;
}
ConstraintInstr* limit = loop->limit();
if (limit == nullptr) {
return nullptr;
}
Definition* def = UnwrapConstraint(limit->value()->definition());
Range* constraining_range = limit->constraint();
if (GetSmiInduction(loop, def) != nullptr &&
constraining_range->min().Equals(RangeBoundary::MinSmi()) &&
constraining_range->max().IsSymbol() &&
def->IsDominatedBy(constraining_range->max().symbol())) {
return loop;
}
return nullptr;
}
// Only accept smi linear induction with unit stride.
InductionVar* GetSmiInduction(LoopInfo* loop, Definition* def) {
if (loop != nullptr && def->Type()->ToCid() == kSmiCid) {
InductionVar* induc = loop->LookupInduction(def);
if (induc != nullptr && induc->kind() == InductionVar::kLinear &&
induc->next()->offset() == 1 && induc->next()->mult() == 0) {
return induc;
}
}
return nullptr;
}
// Reconstruct invariant (phi-init is always already in the graph).
Definition* GenerateInvariant(InductionVar* induc) {
if (induc->mult() == 0) {
return flow_graph_->GetConstant(
Smi::ZoneHandle(Smi::New(induc->offset())));
}
ASSERT(induc->offset() == 0 && induc->mult() == 1);
return induc->def();
}
// Construct symbolic bound for a value at the given point:
//
// 1. if value is an induction variable use its bounds;
@ -1109,8 +997,10 @@ class BoundsCheckGeneralizer {
value = UnwrapConstraint(value);
if (value->IsPhi()) {
PhiInstr* phi = value->AsPhi();
if (phi->induction_variable_info() != NULL) {
return (this->*phi_bound_func)(phi, point);
LoopInfo* loop = GetSmiBoundedLoop(phi);
InductionVar* induc = GetSmiInduction(loop, phi);
if (induc != nullptr) {
return (this->*phi_bound_func)(phi, loop, induc, point);
}
} else if (value->IsBinarySmiOp()) {
BinarySmiOpInstr* bin_op = value->AsBinarySmiOp();
@ -1131,58 +1021,61 @@ class BoundsCheckGeneralizer {
}
}
}
return value;
}
Definition* InductionVariableUpperBound(PhiInstr* phi, Instruction* point) {
const InductionVariableInfo& info = *phi->induction_variable_info();
if (info.bound() == phi) {
if (point->IsDominatedBy(info.limit())) {
// Given induction variable
//
// x <- phi(x0, x + 1)
//
// and a constraint x <= M that dominates the given
// point we conclude that M is an upper bound for x.
return RangeBoundaryToDefinition(info.limit()->constraint()->max());
}
} else {
const InductionVariableInfo& bound_info =
*info.bound()->induction_variable_info();
if (point->IsDominatedBy(bound_info.limit())) {
// Given two induction variables
//
// x <- phi(x0, x + 1)
// y <- phi(y0, y + 1)
//
// and a constraint x <= M that dominates the given
// point we can conclude that
//
// y <= y0 + (M - x0)
//
Definition* limit =
RangeBoundaryToDefinition(bound_info.limit()->constraint()->max());
BinarySmiOpInstr* loop_length = MakeBinaryOp(
Token::kSUB, ConstructUpperBound(limit, point),
ConstructLowerBound(bound_info.initial_value(), point));
return MakeBinaryOp(Token::kADD,
ConstructUpperBound(info.initial_value(), point),
loop_length);
}
Definition* InductionVariableUpperBound(PhiInstr* phi,
LoopInfo* loop,
InductionVar* induc,
Instruction* point) {
// Test if limit dominates given point.
ConstraintInstr* limit = loop->limit();
if (!point->IsDominatedBy(limit)) {
return phi;
}
// Decide between direct or indirect bound.
Definition* bounded_phi = UnwrapConstraint(limit->value()->definition());
if (bounded_phi == phi) {
// Given a smi bounded loop with smi induction variable
//
// x <- phi(x0, x + 1)
//
// and a constraint x <= M that dominates the given
// point we conclude that M is an upper bound for x.
return RangeBoundaryToDefinition(limit->constraint()->max());
} else {
// Given a smi bounded loop with two smi induction variables
//
// x <- phi(x0, x + 1)
// y <- phi(y0, y + 1)
//
// and a constraint x <= M that dominates the given
// point we can conclude that
//
// y <= y0 + (M - x0)
//
InductionVar* bounded_induc = GetSmiInduction(loop, bounded_phi);
Definition* x0 = GenerateInvariant(bounded_induc->initial());
Definition* y0 = GenerateInvariant(induc->initial());
Definition* m = RangeBoundaryToDefinition(limit->constraint()->max());
BinarySmiOpInstr* loop_length =
MakeBinaryOp(Token::kSUB, ConstructUpperBound(m, point),
ConstructLowerBound(x0, point));
return MakeBinaryOp(Token::kADD, ConstructUpperBound(y0, point),
loop_length);
}
return phi;
}
Definition* InductionVariableLowerBound(PhiInstr* phi, Instruction* point) {
// Given induction variable
Definition* InductionVariableLowerBound(PhiInstr* phi,
LoopInfo* loop,
InductionVar* induc,
Instruction* point) {
// Given a smi bounded loop with smi induction variable
//
// x <- phi(x0, x + 1)
//
// we can conclude that LowerBound(x) == x0.
const InductionVariableInfo& info = *phi->induction_variable_info();
return ConstructLowerBound(info.initial_value(), point);
return ConstructLowerBound(GenerateInvariant(induc->initial()), point);
}
// Try to re-associate binary operations in the floating DAG of operations

View file

@ -538,8 +538,8 @@ class RangeAnalysis : public ValueObject {
enum JoinOperator { NONE, WIDEN, NARROW };
static char OpPrefix(JoinOperator op);
// Collect all values that were proven to be smi in smi_values_ array and all
// CheckSmi instructions in smi_check_ array.
// Collect all integer values (smi or int), all 64-bit binary
// and shift operations, and all check bounds.
void CollectValues();
// Iterate over smi values and constrain them at branch successors.
@ -583,8 +583,6 @@ class RangeAnalysis : public ValueObject {
// Convert mint operations that stay within int32 range into Int32 operations.
void NarrowMintToInt32();
void DiscoverSimpleInductionVariables();
// Remove artificial Constraint instructions and replace them with actual
// unconstrained definitions.
void RemoveConstraints();
@ -600,11 +598,11 @@ class RangeAnalysis : public ValueObject {
Range int64_range_;
// Value that are known to be smi or mint.
// All values that are known to be smi or mint.
GrowableArray<Definition*> values_;
// All 64-bit binary and shift operations.
GrowableArray<BinaryInt64OpInstr*> binary_int64_ops_;
GrowableArray<ShiftIntegerOpInstr*> shift_int64_ops_;
// All CheckArrayBound instructions.