Index: runtime/vm/flow_graph_range_analysis.cc |
diff --git a/runtime/vm/flow_graph_range_analysis.cc b/runtime/vm/flow_graph_range_analysis.cc |
index e7268595135fc69c0c29f5866b4f533250618c45..1ace1f80788ae5525e167fc1fa6ad2b87479b938 100644 |
--- a/runtime/vm/flow_graph_range_analysis.cc |
+++ b/runtime/vm/flow_graph_range_analysis.cc |
@@ -21,12 +21,8 @@ DECLARE_FLAG(bool, trace_constant_propagation); |
void RangeAnalysis::Analyze() { |
CollectValues(); |
- |
- if (FLAG_trace_range_analysis) { |
- FlowGraphPrinter::PrintGraph("Range Analysis (BBB)", flow_graph_); |
- } |
- |
InsertConstraints(); |
+ DiscoverSimpleInductionVariables(); |
InferRanges(); |
EliminateRedundantBoundsChecks(); |
MarkUnreachableBlocks(); |
@@ -40,16 +36,189 @@ void RangeAnalysis::Analyze() { |
} |
+static Definition* UnwrapConstraint(Definition* defn) { |
+ while (defn->IsConstraint()) { |
+ defn = defn->AsConstraint()->value()->definition(); |
+ } |
+ 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 false; |
+ } |
+ |
+ if (phi->InputCount() != 2) { |
+ return false; |
+ } |
+ |
+ BitVector* loop_info = phi->block()->loop_info(); |
+ |
+ const intptr_t backedge_idx = |
+ loop_info->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; |
+} |
+ |
+ |
+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) { |
+ 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_trace_range_analysis) { |
+ OS::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() { |
const GrowableArray<Definition*>& initial = |
*flow_graph_->graph_entry()->initial_definitions(); |
for (intptr_t i = 0; i < initial.length(); ++i) { |
Definition* current = initial[i]; |
- if (current->Type()->ToCid() == kSmiCid) { |
- values_.Add(current); |
- } else if (current->IsMintDefinition()) { |
- values_.Add(current); |
- } else if (current->IsInt32Definition()) { |
+ if (IsIntegerDefinition(current)) { |
values_.Add(current); |
} |
} |
@@ -66,11 +235,7 @@ void RangeAnalysis::CollectValues() { |
: *block->AsCatchBlockEntry()->initial_definitions(); |
for (intptr_t i = 0; i < initial.length(); ++i) { |
Definition* current = initial[i]; |
- if (current->Type()->ToCid() == kSmiCid) { |
- values_.Add(current); |
- } else if (current->IsMintDefinition()) { |
- values_.Add(current); |
- } else if (current->IsInt32Definition()) { |
+ if (IsIntegerDefinition(current)) { |
values_.Add(current); |
} |
} |
@@ -80,9 +245,8 @@ void RangeAnalysis::CollectValues() { |
if (join != NULL) { |
for (PhiIterator phi_it(join); !phi_it.Done(); phi_it.Advance()) { |
PhiInstr* current = phi_it.Current(); |
- if (current->Type()->ToCid() == kSmiCid) { |
- values_.Add(current); |
- } else if (current->representation() == kUnboxedInt32) { |
+ if ((current->Type()->ToCid() == kSmiCid) || |
+ (current->representation() == kUnboxedInt32)) { |
values_.Add(current); |
} |
} |
@@ -94,19 +258,13 @@ void RangeAnalysis::CollectValues() { |
Instruction* current = instr_it.Current(); |
Definition* defn = current->AsDefinition(); |
if (defn != NULL) { |
- if ((defn->Type()->ToCid() == kSmiCid) && |
- (defn->ssa_temp_index() != -1)) { |
- values_.Add(defn); |
- } else if ((defn->IsMintDefinition()) && |
- (defn->ssa_temp_index() != -1)) { |
+ if (defn->HasSSATemp() && IsIntegerDefinition(defn)) { |
values_.Add(defn); |
if (defn->IsBinaryMintOp()) { |
binary_mint_ops_.Add(defn->AsBinaryMintOp()); |
} else if (defn->IsShiftMintOp()) { |
shift_mint_ops_.Add(defn->AsShiftMintOp()); |
} |
- } else if (defn->IsInt32Definition()) { |
- values_.Add(defn); |
} |
} else if (current->IsCheckArrayBound()) { |
bounds_checks_.Add(current->AsCheckArrayBound()); |
@@ -238,7 +396,7 @@ ConstraintInstr* RangeAnalysis::InsertConstraintFor(Value* use, |
} |
-void RangeAnalysis::ConstrainValueAfterBranch(Value* use, Definition* defn) { |
+bool RangeAnalysis::ConstrainValueAfterBranch(Value* use, Definition* defn) { |
BranchInstr* branch = use->instruction()->AsBranch(); |
RelationalOpInstr* rel_op = branch->comparison()->AsRelationalOp(); |
if ((rel_op != NULL) && (rel_op->operation_cid() == kSmiCid)) { |
@@ -263,10 +421,7 @@ void RangeAnalysis::ConstrainValueAfterBranch(Value* use, Definition* defn) { |
defn, |
ConstraintSmiRange(op_kind, boundary), |
branch->true_successor()); |
- // Mark true_constraint an artificial use of boundary. This ensures |
- // that constraint's range is recalculated if boundary's range changes. |
if (true_constraint != NULL) { |
- true_constraint->AddDependency(boundary); |
true_constraint->set_target(branch->true_successor()); |
} |
@@ -277,13 +432,14 @@ void RangeAnalysis::ConstrainValueAfterBranch(Value* use, Definition* defn) { |
defn, |
ConstraintSmiRange(Token::NegateComparison(op_kind), boundary), |
branch->false_successor()); |
- // Mark false_constraint an artificial use of boundary. This ensures |
- // that constraint's range is recalculated if boundary's range changes. |
if (false_constraint != NULL) { |
- false_constraint->AddDependency(boundary); |
false_constraint->set_target(branch->false_successor()); |
} |
+ |
+ return true; |
} |
+ |
+ return false; |
} |
@@ -292,7 +448,12 @@ void RangeAnalysis::InsertConstraintsFor(Definition* defn) { |
use != NULL; |
use = use->next_use()) { |
if (use->instruction()->IsBranch()) { |
- ConstrainValueAfterBranch(use, defn); |
+ if (ConstrainValueAfterBranch(use, defn)) { |
+ Value* other_value = use->instruction()->InputAt(1 - use->use_index()); |
+ if (!IsIntegerDefinition(other_value->definition())) { |
+ ConstrainValueAfterBranch(other_value, other_value->definition()); |
+ } |
+ } |
} else if (use->instruction()->IsCheckArrayBound()) { |
ConstrainValueAfterCheckArrayBound(use, defn); |
} |
@@ -356,14 +517,6 @@ const Range* RangeAnalysis::GetSmiRange(Value* value) const { |
} |
-static Definition* UnwrapConstraint(Definition* defn) { |
- while (defn->IsConstraint()) { |
- defn = defn->AsConstraint()->value()->definition(); |
- } |
- return defn; |
-} |
- |
- |
static bool AreEqualDefinitions(Definition* a, Definition* b) { |
a = UnwrapConstraint(a); |
b = UnwrapConstraint(b); |
@@ -526,7 +679,7 @@ bool RangeAnalysis::InferRange(JoinOperator op, |
} |
-void RangeAnalysis::CollectDefinitions(BlockEntryInstr* block, BitVector* set) { |
+void RangeAnalysis::CollectDefinitions(BitVector* set) { |
for (BlockIterator block_it = flow_graph_->reverse_postorder_iterator(); |
!block_it.Done(); |
block_it.Advance()) { |
@@ -597,7 +750,7 @@ void RangeAnalysis::InferRanges() { |
definitions_.Add(definition); |
} |
} |
- CollectDefinitions(flow_graph_->graph_entry(), set); |
+ CollectDefinitions(set); |
// Perform an iteration of range inference just propagating ranges |
// through the graph as-is without applying widening or narrowing. |
@@ -625,16 +778,718 @@ void RangeAnalysis::InferRanges() { |
} |
+void RangeAnalysis::AssignRangesRecursively(Definition* defn) { |
+ if (!Range::IsUnknown(defn->range())) { |
+ return; |
+ } |
+ |
+ if (!IsIntegerDefinition(defn)) { |
+ return; |
+ } |
+ |
+ for (intptr_t i = 0; i < defn->InputCount(); i++) { |
+ Definition* input_defn = defn->InputAt(i)->definition(); |
+ if (!input_defn->HasSSATemp() || input_defn->IsConstant()) { |
+ AssignRangesRecursively(input_defn); |
+ } |
+ } |
+ |
+ Range new_range; |
+ defn->InferRange(this, &new_range); |
+ if (!Range::IsUnknown(&new_range)) { |
+ defn->set_range(new_range); |
+ } |
+} |
+ |
+ |
+// Scheduler is a helper class that inserts floating control-flow less |
+// subgraphs into the flow graph. |
+// It always attempts to schedule instructions into the loop preheader in the |
+// way similar to LICM optimization pass. |
+// Scheduler supports rollback - that is it keeps track of instructions it |
+// schedules and can remove all instructions it inserted from the graph. |
+class Scheduler { |
+ public: |
+ explicit Scheduler(FlowGraph* flow_graph) |
+ : flow_graph_(flow_graph), |
+ loop_headers_(flow_graph->LoopHeaders()), |
+ pre_headers_(loop_headers_.length()) { |
+ for (intptr_t i = 0; i < loop_headers_.length(); i++) { |
+ pre_headers_.Add(loop_headers_[i]->ImmediateDominator()); |
+ } |
+ } |
+ |
+ // Clear the list of emitted instructions. |
+ void Start() { |
+ emitted_.Clear(); |
+ } |
+ |
+ // Given the floating instruction attempt to schedule it into one of the |
+ // loop preheaders that dominates given post_dominator instruction. |
+ // Some of the instruction inputs can potentially be unscheduled as well. |
+ // Returns NULL is the scheduling fails (e.g. inputs are not invariant for |
+ // any loop containing post_dominator). |
+ // Resulting schedule should be equivalent to one obtained by inserting |
+ // instructions right before post_dominator and running CSE and LICM passes. |
+ template<typename T> |
+ T* Emit(T* instruction, Instruction* post_dominator) { |
Florian Schneider
2014/10/07 11:40:48
Maybe rename post_dominator to sink?
Vyacheslav Egorov (Google)
2014/10/07 18:57:27
While I really like the name 'sink' and use it int
|
+ return static_cast<T*>(EmitRecursively(instruction, post_dominator)); |
+ } |
+ |
+ // Undo all insertions recorded in the list of emitted instructions. |
+ void Rollback() { |
+ for (intptr_t i = emitted_.length() - 1; i >= 0; i--) { |
+ emitted_[i]->RemoveFromGraph(); |
+ } |
+ emitted_.Clear(); |
+ } |
+ |
+ private: |
+ typedef DirectChainedHashMap<PointerKeyValueTrait<Instruction> > Map; |
+ |
+ Instruction* EmitRecursively(Instruction* instruction, Instruction* sink) { |
+ // Schedule all unscheduled inputs and unwrap all constrained inputs. |
+ for (intptr_t i = 0; i < instruction->InputCount(); i++) { |
+ Definition* defn = instruction->InputAt(i)->definition(); |
+ if (defn->ssa_temp_index() == -1) { |
Florian Schneider
2014/10/07 11:40:48
!HasSSATemp()
Vyacheslav Egorov (Google)
2014/10/07 18:57:27
Done.
|
+ Definition* scheduled = Emit(defn, sink); |
+ if (scheduled == NULL) { |
+ return NULL; |
+ } |
+ instruction->InputAt(i)->set_definition(scheduled); |
Florian Schneider
2014/10/07 11:40:48
Do we care about stale entries in the use-list of
Vyacheslav Egorov (Google)
2014/10/07 18:57:27
Instruction itself is not in the graph - which mea
|
+ } else if (defn->IsConstraint()) { |
+ instruction->InputAt(i)->set_definition(UnwrapConstraint(defn)); |
Florian Schneider
2014/10/07 11:40:48
Same issue as above.
Vyacheslav Egorov (Google)
2014/10/07 18:57:27
Same answer as above :)
|
+ } |
+ } |
+ |
+ // Attempt to find equivalent instruction that was already scheduled. |
+ // If the instruction is still in the graph (it could have been |
+ // un-scheduled by a rollback action) and it dominates the sink - use it. |
+ Instruction* emitted = map_.Lookup(instruction); |
+ if (emitted != NULL && |
+ !emitted->WasEliminated() && |
+ sink->IsDominatedBy(emitted)) { |
+ return emitted; |
+ } |
+ |
+ // Attempt to find suitable pre-header. Iterate loop headers backwards to |
+ // attempt scheduling into the outermost loop first. |
+ for (intptr_t i = loop_headers_.length() - 1; i >= 0; i--) { |
+ BlockEntryInstr* header = loop_headers_[i]; |
+ BlockEntryInstr* pre_header = pre_headers_[i]; |
+ |
+ if (pre_header == NULL) { |
+ continue; |
+ } |
+ |
+ if (!sink->IsDominatedBy(header)) { |
+ continue; |
+ } |
+ |
+ Instruction* last = pre_header->last_instruction(); |
+ |
+ bool inputs_are_invariant = true; |
+ for (intptr_t j = 0; j < instruction->InputCount(); j++) { |
+ Definition* defn = instruction->InputAt(j)->definition(); |
+ if (!last->IsDominatedBy(defn)) { |
+ inputs_are_invariant = false; |
+ break; |
+ } |
+ } |
+ |
+ if (inputs_are_invariant) { |
+ EmitTo(pre_header, instruction); |
+ return instruction; |
+ } |
+ } |
+ |
+ return NULL; |
+ } |
+ |
+ void EmitTo(BlockEntryInstr* block, Instruction* instr) { |
+ GotoInstr* last = block->last_instruction()->AsGoto(); |
+ flow_graph_->InsertBefore(last, |
+ instr, |
+ last->env(), |
+ instr->IsDefinition() ? FlowGraph::kValue |
+ : FlowGraph::kEffect); |
+ instr->deopt_id_ = last->GetDeoptId(); |
+ instr->env()->set_deopt_id(instr->deopt_id_); |
+ |
+ map_.Insert(instr); |
+ emitted_.Add(instr); |
+ } |
+ |
+ FlowGraph* flow_graph_; |
+ Map map_; |
+ const ZoneGrowableArray<BlockEntryInstr*>& loop_headers_; |
+ GrowableArray<BlockEntryInstr*> pre_headers_; |
+ GrowableArray<Instruction*> emitted_; |
+}; |
+ |
+ |
+// If bounds check 0 <= index < length is not redundant we attempt to |
+// replace it with a sequence of checks that guarantee |
+// |
+// 0 <= LowerBound(index) < UpperBound(index) < length |
+// |
+// and hoist all of those checks out of the enclosing loop. |
+// |
+// Upper/Lower bounds are symbolic arithmetic expressions with +, -, * |
+// operations. |
+class BoundsCheckGeneralizer { |
+ public: |
+ BoundsCheckGeneralizer(RangeAnalysis* range_analysis, |
+ FlowGraph* flow_graph) |
+ : range_analysis_(range_analysis), |
+ flow_graph_(flow_graph), |
+ scheduler_(flow_graph) { } |
+ |
+ void TryGeneralize(CheckArrayBoundInstr* check, |
+ const RangeBoundary& array_length) { |
+ Definition* upper_bound = |
+ ConstructUpperBound(check->index()->definition(), check); |
+ if (upper_bound == UnwrapConstraint(check->index()->definition())) { |
+ // Unable to construct upper bound for the index. |
+ if (FLAG_trace_range_analysis) { |
+ OS::Print("Failed to construct upper bound for %s index\n", |
+ check->ToCString()); |
+ } |
+ return; |
+ } |
+ |
+ // Re-associate subexpressions inside upper_bound to collect all constants |
+ // together. This will expose more redundancies when we are going to emit |
+ // upper bound through scheduler. |
+ if (!Simplify(&upper_bound, NULL)) { |
+ if (FLAG_trace_range_analysis) { |
+ OS::Print("Failed to simplify upper bound for %s index\n", |
+ check->ToCString()); |
+ } |
+ return; |
+ } |
+ upper_bound = ApplyConstraints(upper_bound, check); |
+ range_analysis_->AssignRangesRecursively(upper_bound); |
+ |
+ // We are going to constrain any symbols participating in + and * operations |
+ // to guarantee that they are positive. Find all symbols that need |
+ // constraining. If there is a subtraction subexpression with non-positive |
+ // range give up on generalization for simplicity. |
+ GrowableArray<Definition*> non_positive_symbols; |
+ if (!FindNonPositiveSymbols(&non_positive_symbols, upper_bound)) { |
+ if (FLAG_trace_range_analysis) { |
+ OS::Print("Failed to generalize %s index to %s" |
+ " (can't ensure positivity)\n", |
+ check->ToCString(), |
+ IndexBoundToCString(upper_bound)); |
+ } |
+ return; |
+ } |
+ |
+ // Check that we can statically prove that lower bound of the index is |
+ // non-negative under the assumption that all potentially non-positive |
+ // symbols are positive. |
+ GrowableArray<ConstraintInstr*> positive_constraints( |
+ non_positive_symbols.length()); |
+ Range* positive_range = new Range( |
+ RangeBoundary::FromConstant(0), |
+ RangeBoundary::MaxConstant(RangeBoundary::kRangeBoundarySmi)); |
+ for (intptr_t i = 0; i < non_positive_symbols.length(); i++) { |
+ Definition* symbol = non_positive_symbols[i]; |
+ positive_constraints.Add(new ConstraintInstr( |
+ new Value(symbol), |
+ positive_range)); |
+ } |
+ |
+ Definition* lower_bound = |
+ ConstructLowerBound(check->index()->definition(), check); |
+ // No need to simplify lower bound before applying constraints as |
+ // we are not going to emit it. |
+ lower_bound = ApplyConstraints(lower_bound, check, &positive_constraints); |
+ range_analysis_->AssignRangesRecursively(lower_bound); |
+ |
+ if (!RangeUtils::IsPositive(lower_bound->range())) { |
+ // Can't prove that lower bound is positive even with additional checks |
+ // against potentially non-positive symbols. Give up. |
+ if (FLAG_trace_range_analysis) { |
+ OS::Print("Failed to generalize %s index to %s" |
+ " (lower bound is not positive)\n", |
+ check->ToCString(), |
+ IndexBoundToCString(upper_bound)); |
+ } |
+ return; |
+ } |
+ |
+ if (FLAG_trace_range_analysis) { |
+ OS::Print("For %s computed index bounds [%s, %s]\n", |
+ check->ToCString(), |
+ IndexBoundToCString(lower_bound), |
+ IndexBoundToCString(upper_bound)); |
+ } |
+ |
+ // At this point we know that 0 <= index < UpperBound(index) under |
+ // certain preconditions. Start by emitting this preconditions. |
+ scheduler_.Start(); |
+ |
+ ConstantInstr* max_smi = |
+ flow_graph_->GetConstant(Smi::Handle(Smi::New(Smi::kMaxValue))); |
+ for (intptr_t i = 0; i < non_positive_symbols.length(); i++) { |
+ CheckArrayBoundInstr* precondition = new CheckArrayBoundInstr( |
+ new Value(max_smi), |
+ new Value(non_positive_symbols[i]), |
+ Isolate::kNoDeoptId); |
+ precondition->mark_generalized(); |
+ precondition = scheduler_.Emit(precondition, check); |
+ if (precondition == NULL) { |
+ if (FLAG_trace_range_analysis) { |
+ OS::Print(" => failed to insert positivity constraint\n"); |
+ } |
+ scheduler_.Rollback(); |
+ return; |
+ } |
+ } |
+ |
+ CheckArrayBoundInstr* new_check = new CheckArrayBoundInstr( |
+ new Value(UnwrapConstraint(check->length()->definition())), |
+ new Value(upper_bound), |
+ Isolate::kNoDeoptId); |
+ new_check->mark_generalized(); |
+ if (new_check->IsRedundant(array_length)) { |
+ if (FLAG_trace_range_analysis) { |
+ OS::Print(" => generalized check is redundant\n"); |
+ } |
+ RemoveGeneralizedCheck(check); |
+ return; |
+ } |
+ |
+ new_check = scheduler_.Emit(new_check, check); |
+ if (new_check != NULL) { |
+ if (FLAG_trace_range_analysis) { |
+ OS::Print(" => generalized check was hoisted into B%" Pd "\n", |
+ new_check->GetBlock()->block_id()); |
+ } |
+ RemoveGeneralizedCheck(check); |
+ } else { |
+ if (FLAG_trace_range_analysis) { |
+ OS::Print(" => generalized check can't be hoisted\n"); |
+ } |
+ scheduler_.Rollback(); |
+ } |
+ } |
+ |
+ static void RemoveGeneralizedCheck(CheckArrayBoundInstr* check) { |
+ BinarySmiOpInstr* binary_op = |
+ check->index()->definition()->AsBinarySmiOp(); |
+ if (binary_op != NULL) { |
+ binary_op->set_can_overflow(false); |
+ } |
+ check->RemoveFromGraph(); |
+ } |
+ |
+ private: |
+ BinarySmiOpInstr* MakeBinaryOp(Token::Kind op_kind, |
+ Definition* left, |
+ Definition* right) { |
+ return new BinarySmiOpInstr(op_kind, |
+ new Value(left), |
+ new Value(right), |
+ Isolate::kNoDeoptId); |
+ } |
+ |
+ |
+ BinarySmiOpInstr* MakeBinaryOp(Token::Kind op_kind, |
+ Definition* left, |
+ intptr_t right) { |
+ ConstantInstr* constant_right = |
+ flow_graph_->GetConstant(Smi::Handle(Smi::New(right))); |
+ return MakeBinaryOp(op_kind, left, constant_right); |
+ } |
+ |
+ Definition* RangeBoundaryToDefinition(const RangeBoundary& bound) { |
+ Definition* symbol = UnwrapConstraint(bound.symbol()); |
+ if (bound.offset() == 0) { |
+ return symbol; |
+ } else { |
+ return MakeBinaryOp(Token::kADD, symbol, bound.offset()); |
+ } |
+ } |
+ |
+ typedef Definition* (BoundsCheckGeneralizer::*PhiBoundFunc)( |
+ PhiInstr*, Instruction*); |
+ |
+ // Construct symbolic lower bound for a value at the given point. |
+ Definition* ConstructLowerBound(Definition* value, Instruction* point) { |
+ return ConstructBound(&BoundsCheckGeneralizer::InductionVariableLowerBound, |
+ value, |
+ point); |
+ } |
+ |
+ // Construct symbolic upper bound for a value at the given point. |
+ Definition* ConstructUpperBound(Definition* value, Instruction* point) { |
+ return ConstructBound(&BoundsCheckGeneralizer::InductionVariableUpperBound, |
+ value, |
+ point); |
+ } |
+ |
+ // Construct symbolic bound for a value at the given point: |
+ // |
+ // 1. if value is an induction variable use its bounds; |
+ // 2. if value is addition or multiplication construct bounds for left |
+ // and right hand sides separately and use addition/multiplication |
+ // of bounds as a bound (addition and multiplication are monotone |
+ // operations for both operands); |
+ // 3. if value is a substraction then construct bound for the left hand |
+ // side and use substraction of the right hand side from the left hand |
+ // side bound as a bound for an expression (substraction is monotone for |
+ // the left hand side operand). |
+ // |
+ Definition* ConstructBound(PhiBoundFunc phi_bound_func, |
+ Definition* value, |
+ Instruction* point) { |
+ value = UnwrapConstraint(value); |
+ if (PhiInstr* phi = value->AsPhi()) { |
+ if (phi->induction_variable_info() != NULL) { |
+ return (this->*phi_bound_func)(phi, point); |
+ } |
+ } else if (BinarySmiOpInstr* bin_op = value->AsBinarySmiOp()) { |
+ if ((bin_op->op_kind() == Token::kADD) || |
+ (bin_op->op_kind() == Token::kMUL) || |
+ (bin_op->op_kind() == Token::kSUB)) { |
+ Definition* new_left = |
+ ConstructBound(phi_bound_func, bin_op->left()->definition(), point); |
+ Definition* new_right = (bin_op->op_kind() != Token::kSUB) |
+ ? ConstructBound(phi_bound_func, |
+ bin_op->right()->definition(), |
+ point) |
+ : UnwrapConstraint(bin_op->right()->definition()); |
+ |
+ if ((new_left != UnwrapConstraint(bin_op->left()->definition())) || |
+ (new_right != UnwrapConstraint(bin_op->right()->definition()))) { |
+ return MakeBinaryOp(bin_op->op_kind(), new_left, new_right); |
+ } |
+ } |
+ } |
+ |
+ 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); |
+ } |
+ } |
+ |
+ return phi; |
+ } |
+ |
+ Definition* InductionVariableLowerBound(PhiInstr* phi, |
+ Instruction* point) { |
+ // Given 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); |
+ } |
+ |
+ // Try to re-associate binary operations in the floating DAG of operations |
+ // to collect all constants together, e.g. x + C0 + y + C1 is simplified into |
+ // x + y + (C0 + C1). |
+ bool Simplify(Definition** defn, intptr_t* constant) { |
+ if (BinarySmiOpInstr* binary_op = (*defn)->AsBinarySmiOp()) { |
+ Definition* left = binary_op->left()->definition(); |
+ Definition* right = binary_op->right()->definition(); |
+ |
+ if (binary_op->op_kind() == Token::kADD) { |
+ intptr_t left_const = 0; |
+ intptr_t right_const = 0; |
+ if (!Simplify(&left, &left_const) || !Simplify(&right, &right_const)) { |
+ return false; |
+ } |
+ |
+ const intptr_t c = left_const + right_const; |
+ if (Utils::WillAddOverflow(left_const, right_const) || |
+ !Smi::IsValid(c)) { |
+ return false; // Abort. |
+ } |
+ |
+ if (constant != NULL) { |
+ *constant = c; |
+ } |
+ |
+ if (left == NULL && right == NULL) { |
+ if (constant != NULL) { |
+ *defn = NULL; |
+ } else { |
+ *defn = flow_graph_->GetConstant(Smi::Handle(Smi::New(c))); |
+ } |
+ return true; |
+ } |
+ |
+ if (left == NULL) { |
+ if (constant != NULL || c == 0) { |
+ *defn = right; |
+ return true; |
+ } else { |
+ left = right; |
+ right = NULL; |
+ } |
+ } |
+ |
+ if (right == NULL) { |
+ if (constant != NULL || c == 0) { |
+ *defn = right; |
+ return true; |
+ } else { |
+ right = flow_graph_->GetConstant(Smi::Handle(Smi::New(c))); |
+ } |
+ } |
+ |
+ } else if (binary_op->op_kind() == Token::kSUB) { |
+ intptr_t left_const = 0; |
+ intptr_t right_const = 0; |
+ if (!Simplify(&left, &left_const) || !Simplify(&right, &right_const)) { |
+ return false; |
+ } |
+ |
+ const intptr_t c = (left_const - right_const); |
+ if (Utils::WillSubOverflow(left_const, right_const) || |
+ !Smi::IsValid(c)) { |
+ return false; // Abort. |
+ } |
+ |
+ if (constant != NULL) { |
+ *constant = c; |
+ } |
+ |
+ if (left == NULL && right == NULL) { |
+ if (constant != NULL) { |
+ *defn = NULL; |
+ } else { |
+ *defn = flow_graph_->GetConstant(Smi::Handle(Smi::New(c))); |
+ } |
+ return true; |
+ } |
+ |
+ if (left == NULL) { |
+ left = flow_graph_->GetConstant(Smi::Handle(Smi::New(0))); |
+ } |
+ |
+ if (right == NULL) { |
+ if (constant != NULL || c == 0) { |
+ *defn = left; |
+ return true; |
+ } else { |
+ right = flow_graph_->GetConstant(Smi::Handle(Smi::New(-c))); |
+ } |
+ } |
+ |
+ } else { |
+ if (!Simplify(&left, NULL) || !Simplify(&right, NULL)) { |
+ return false; |
+ } |
+ } |
+ |
+ ASSERT(left != NULL); |
+ ASSERT(right != NULL); |
+ |
+ const bool left_changed = (left != binary_op->left()->definition()); |
+ const bool right_changed = (right != binary_op->right()->definition()); |
+ if (left_changed || right_changed) { |
+ if ((*defn)->ssa_temp_index() == -1) { |
+ if (left_changed) binary_op->left()->set_definition(left); |
+ if (right_changed) binary_op->right()->set_definition(right); |
+ *defn = binary_op; |
+ } else { |
+ *defn = MakeBinaryOp(binary_op->op_kind(), |
+ UnwrapConstraint(left), |
+ UnwrapConstraint(right)); |
+ } |
+ } |
+ } else if (ConstantInstr* constant_defn = (*defn)->AsConstant()) { |
+ if ((constant != NULL) && constant_defn->value().IsSmi()) { |
+ *defn = NULL; |
+ *constant = Smi::Cast(constant_defn->value()).Value(); |
+ } |
+ } |
+ |
+ return true; |
+ } |
+ |
+ // If possible find a set of symbols that need to be non-negative to |
+ // guarantee that expression as whole is non-negative. |
+ bool FindNonPositiveSymbols(GrowableArray<Definition*>* symbols, |
+ Definition* defn) { |
+ if (defn->IsConstant()) { |
+ const Object& value = defn->AsConstant()->value(); |
+ return value.IsSmi() && (Smi::Cast(value).Value() >= 0); |
+ } else if (defn->HasSSATemp()) { |
+ if (!RangeUtils::IsPositive(defn->range())) { |
+ symbols->Add(defn); |
+ } |
+ return true; |
+ } else if (defn->IsBinarySmiOp()) { |
+ BinarySmiOpInstr* binary_op = defn->AsBinarySmiOp(); |
+ ASSERT((binary_op->op_kind() == Token::kADD) || |
+ (binary_op->op_kind() == Token::kSUB) || |
+ (binary_op->op_kind() == Token::kMUL)); |
+ |
+ if (RangeUtils::IsPositive(defn->range())) { |
+ // We can statically prove that this subexpression is always positive. |
+ // No need to inspect its subexpressions. |
+ return true; |
+ } |
+ |
+ if (binary_op->op_kind() == Token::kSUB) { |
+ // For addition and multiplication it's enough to ensure that |
+ // lhs and rhs are positive to guarantee that defn as whole is |
+ // positive. This does not work for substraction so just give up. |
+ return false; |
+ } |
+ |
+ return FindNonPositiveSymbols(symbols, binary_op->left()->definition()) && |
+ FindNonPositiveSymbols(symbols, binary_op->right()->definition()); |
+ } |
+ UNREACHABLE(); |
+ return false; |
+ } |
+ |
+ // Find innermost constraint for the given definition dominating given |
+ // instruction. |
+ static Definition* FindInnermostConstraint(Definition* defn, |
+ Instruction* post_dominator) { |
+ for (Value* use = defn->input_use_list(); |
+ use != NULL; |
+ use = use->next_use()) { |
+ ConstraintInstr* constraint = use->instruction()->AsConstraint(); |
+ if ((constraint != NULL) && post_dominator->IsDominatedBy(constraint)) { |
+ return FindInnermostConstraint(constraint, post_dominator); |
+ } |
+ } |
+ return defn; |
+ } |
+ |
+ // Replace symbolic parts of the boundary with respective constraints |
+ // that hold at the given point in the flow graph signified by |
+ // post_dominator. |
+ // Constraints array allows to provide a set of additional floating |
+ // constraints that were not inserted into the graph. |
+ static Definition* ApplyConstraints( |
+ Definition* defn, |
+ Instruction* post_dominator, |
Florian Schneider
2014/10/07 11:40:48
I'd rename post_dominator to
CheckArrayBoundInstr
Vyacheslav Egorov (Google)
2014/10/07 18:57:27
It does not have to be CheckArrayBoundInstr though
|
+ GrowableArray<ConstraintInstr*>* constraints = NULL) { |
+ if (defn->HasSSATemp()) { |
+ defn = FindInnermostConstraint(defn, post_dominator); |
+ if (constraints != NULL) { |
+ for (intptr_t i = 0; i < constraints->length(); i++) { |
+ ConstraintInstr* constraint = (*constraints)[i]; |
+ if (constraint->value()->definition() == defn) { |
+ return constraint; |
+ } |
+ } |
+ } |
+ return defn; |
+ } |
+ |
+ for (intptr_t i = 0; i < defn->InputCount(); i++) { |
+ defn->InputAt(i)->set_definition( |
+ ApplyConstraints(defn->InputAt(i)->definition(), |
+ post_dominator, |
+ constraints)); |
+ } |
+ |
+ return defn; |
+ } |
+ |
+ static void PrettyPrintIndexBoundRecursively(BufferFormatter* f, |
+ Definition* index_bound) { |
+ BinarySmiOpInstr* binary_op = index_bound->AsBinarySmiOp(); |
+ if (binary_op != NULL) { |
+ f->Print("("); |
+ PrettyPrintIndexBoundRecursively(f, binary_op->left()->definition()); |
+ f->Print(" %s ", Token::Str(binary_op->op_kind())); |
+ PrettyPrintIndexBoundRecursively(f, binary_op->right()->definition()); |
+ f->Print(")"); |
+ } else if (index_bound->IsConstant()) { |
+ f->Print("%" Pd "", |
+ Smi::Cast(index_bound->AsConstant()->value()).Value()); |
+ } else { |
+ f->Print("v%" Pd "", index_bound->ssa_temp_index()); |
+ } |
+ f->Print(" {%s}", Range::ToCString(index_bound->range())); |
+ } |
+ |
+ |
+ static const char* IndexBoundToCString(Definition* index_bound) { |
+ char buffer[1024]; |
+ BufferFormatter f(buffer, sizeof(buffer)); |
+ PrettyPrintIndexBoundRecursively(&f, index_bound); |
+ return Isolate::Current()->current_zone()->MakeCopyOfString(buffer); |
+ } |
+ |
+ RangeAnalysis* range_analysis_; |
+ FlowGraph* flow_graph_; |
+ Scheduler scheduler_; |
+}; |
+ |
+ |
void RangeAnalysis::EliminateRedundantBoundsChecks() { |
if (FLAG_array_bounds_check_elimination) { |
+ const Function& function = flow_graph_->parsed_function().function(); |
+ const bool try_generalization = |
+ function.allows_bounds_check_generalization(); |
+ |
+ BoundsCheckGeneralizer generalizer(this, flow_graph_); |
+ |
for (intptr_t i = 0; i < bounds_checks_.length(); i++) { |
CheckArrayBoundInstr* check = bounds_checks_[i]; |
RangeBoundary array_length = |
RangeBoundary::FromDefinition(check->length()->definition()); |
if (check->IsRedundant(array_length)) { |
check->RemoveFromGraph(); |
+ } else if (try_generalization) { |
+ generalizer.TryGeneralize(check, array_length); |
} |
} |
+ |
+ if (FLAG_trace_range_analysis) { |
+ FlowGraphPrinter::PrintGraph("RangeAnalysis (ABCE)", flow_graph_); |
+ } |
} |
} |
@@ -1400,11 +2255,7 @@ bool Range::OnlyLessThanOrEqualTo(int64_t val) const { |
// Cannot be true. |
return false; |
} |
- if (max().UpperBound().ConstantValue() > val) { |
- // Not true. |
- return false; |
- } |
- return true; |
+ return max().UpperBound().ConstantValue() <= val; |
} |
@@ -1412,10 +2263,7 @@ bool Range::OnlyGreaterThanOrEqualTo(int64_t val) const { |
if (min().IsNegativeInfinity()) { |
return false; |
} |
- if (min().LowerBound().ConstantValue() < val) { |
- return false; |
- } |
- return true; |
+ return min().LowerBound().ConstantValue() >= val; |
} |
@@ -1426,10 +2274,8 @@ bool Range::IsWithin(int64_t min_int, int64_t max_int) const { |
return false; |
} |
RangeBoundary upper_max = max().UpperBound(); |
- if (upper_max.IsPositiveInfinity() || (upper_max.ConstantValue() > max_int)) { |
- return false; |
- } |
- return true; |
+ return !upper_max.IsPositiveInfinity() && |
+ (upper_max.ConstantValue() <= max_int); |
} |
@@ -1458,10 +2304,7 @@ bool Range::IsUnsatisfiable() const { |
return true; |
} |
// Symbol case: For example [v+1, v]. |
- if (DependOnSameSymbol(min(), max()) && min().offset() > max().offset()) { |
- return true; |
- } |
- return false; |
+ return DependOnSameSymbol(min(), max()) && min().offset() > max().offset(); |
} |
@@ -2148,7 +2991,15 @@ bool CheckArrayBoundInstr::IsRedundant(const RangeBoundary& length) { |
// Range of the index is unknown can't decide if the check is redundant. |
if (index_range == NULL) { |
- return false; |
+ if (!(index()->BindsToConstant() && index()->BoundConstant().IsSmi())) { |
+ return false; |
+ } |
+ |
+ Range range; |
+ index()->definition()->InferRange(NULL, &range); |
+ ASSERT(!Range::IsUnknown(&range)); |
+ index()->definition()->set_range(range); |
+ index_range = index()->definition()->range(); |
} |
// Range of the index is not positive. Check can't be redundant. |
@@ -2156,8 +3007,9 @@ bool CheckArrayBoundInstr::IsRedundant(const RangeBoundary& length) { |
return false; |
} |
- RangeBoundary max = CanonicalizeBoundary(index_range->max(), |
- RangeBoundary::PositiveInfinity()); |
+ RangeBoundary max = CanonicalizeBoundary( |
+ RangeBoundary::FromDefinition(index()->definition()), |
+ RangeBoundary::PositiveInfinity()); |
if (max.OverflowedSmi()) { |
return false; |