[vm/compiler] AOT bounds check elimination
Rationale:
With the new induction variable bounds in place, this
is the first very low hanging fruit. When a loop bound
matches the array bounds check, it is redundant. This
removes AOT non-speculative bounds checks for the first
time. More improvements to follow!
Change-Id: I5dac885a1ef6fdb64b5844a0617d2fe9d800d9d6
Reviewed-on: https://dart-review.googlesource.com/c/87940
Commit-Queue: Aart Bik <ajcbik@google.com>
Reviewed-by: Vyacheslav Egorov <vegorov@google.com>
diff --git a/runtime/vm/compiler/backend/il.h b/runtime/vm/compiler/backend/il.h
index 9d4fc4e..8bbb6d1 100644
--- a/runtime/vm/compiler/backend/il.h
+++ b/runtime/vm/compiler/backend/il.h
@@ -7276,6 +7276,8 @@
// ArgumentError constructor), so it can lazily deopt.
virtual bool ComputeCanDeoptimize() const { return true; }
+ bool IsRedundant(const RangeBoundary& length);
+
// Give a name to the location/input indices.
enum { kLengthPos = 0, kIndexPos = 1 };
diff --git a/runtime/vm/compiler/backend/range_analysis.cc b/runtime/vm/compiler/backend/range_analysis.cc
index 7fa08c0..28f2199 100644
--- a/runtime/vm/compiler/backend/range_analysis.cc
+++ b/runtime/vm/compiler/backend/range_analysis.cc
@@ -107,8 +107,8 @@
}
}
}
- if (current->IsCheckArrayBound()) {
- bounds_checks_.Add(current->AsCheckArrayBound());
+ if (current->IsCheckArrayBound() || current->IsGenericCheckBound()) {
+ bounds_checks_.Add(current);
}
}
}
@@ -1017,8 +1017,8 @@
return phi;
}
// Decide between direct or indirect bound.
- Definition* bounded_phi = UnwrapConstraint(limit->value()->definition());
- if (bounded_phi == phi) {
+ Definition* bounded_def = UnwrapConstraint(limit->value()->definition());
+ if (bounded_def == phi) {
// Given a smi bounded loop with smi induction variable
//
// x <- phi(x0, x + 1)
@@ -1037,7 +1037,7 @@
//
// y <= y0 + (M - x0)
//
- InductionVar* bounded_induc = GetSmiInduction(loop, bounded_phi);
+ InductionVar* bounded_induc = GetSmiInduction(loop, bounded_def);
Definition* x0 = GenerateInvariant(bounded_induc->initial());
Definition* y0 = GenerateInvariant(induc->initial());
Definition* m = RangeBoundaryToDefinition(limit->constraint()->max());
@@ -1321,7 +1321,21 @@
BoundsCheckGeneralizer generalizer(this, flow_graph_);
for (intptr_t i = 0; i < bounds_checks_.length(); i++) {
- CheckArrayBoundInstr* check = bounds_checks_[i];
+ // Is this a non-speculative check bound?
+ GenericCheckBoundInstr* aot_check =
+ bounds_checks_[i]->AsGenericCheckBound();
+ if (aot_check != nullptr) {
+ RangeBoundary array_length =
+ RangeBoundary::FromDefinition(aot_check->length()->definition());
+ if (aot_check->IsRedundant(array_length)) {
+ aot_check->ReplaceUsesWith(aot_check->index()->definition());
+ aot_check->RemoveFromGraph();
+ }
+ continue;
+ }
+ // Must be a speculative check bound.
+ CheckArrayBoundInstr* check = bounds_checks_[i]->AsCheckArrayBound();
+ ASSERT(check != nullptr);
RangeBoundary array_length =
RangeBoundary::FromDefinition(check->length()->definition());
if (check->IsRedundant(array_length)) {
@@ -2928,6 +2942,52 @@
return false;
}
+// Check if range boundary and invariant limit are the same boundary.
+static bool IsSameBound(const RangeBoundary& a, InductionVar* b) {
+ ASSERT(InductionVar::IsInvariant(b));
+ if (a.IsSymbol()) {
+ // Check for exactly the same symbol as length.
+ return a.symbol() == b->def() && b->mult() == 1 &&
+ a.offset() == b->offset();
+ } else if (a.IsConstant()) {
+ // Check for constant in right range 0 < c <= length.
+ int64_t c = 0;
+ return InductionVar::IsConstant(b, &c) && 0 < c && c <= a.ConstantValue();
+ }
+ return false;
+}
+
+bool GenericCheckBoundInstr::IsRedundant(const RangeBoundary& length) {
+ // In loop, with index as induction?
+ LoopInfo* loop = GetBlock()->loop_info();
+ if (loop == nullptr) {
+ return false;
+ }
+ InductionVar* induc = loop->LookupInduction(index()->definition());
+ if (induc == nullptr) {
+ return false;
+ }
+ // Under 64-bit wrap-around arithmetic, it is always safe to remove the
+ // bounds check from the following, if initial >= 0 and the corresponding
+ // exit branch dominates the bounds check:
+ // for (int i = initial; i < length; i++)
+ // .... a[i] ....
+ int64_t stride = 0;
+ int64_t initial = 0;
+ if (InductionVar::IsLinear(induc, &stride) &&
+ InductionVar::IsConstant(induc->initial(), &initial)) {
+ if (stride == 1 && initial >= 0) {
+ for (auto bound : induc->bounds()) {
+ if (IsSameBound(length, bound.limit_) &&
+ this->IsDominatedBy(bound.branch_)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
} // namespace dart
#endif // !defined(DART_PRECOMPILED_RUNTIME)
diff --git a/runtime/vm/compiler/backend/range_analysis.h b/runtime/vm/compiler/backend/range_analysis.h
index 56dc78e..24adeed 100644
--- a/runtime/vm/compiler/backend/range_analysis.h
+++ b/runtime/vm/compiler/backend/range_analysis.h
@@ -605,8 +605,8 @@
GrowableArray<BinaryInt64OpInstr*> binary_int64_ops_;
GrowableArray<ShiftIntegerOpInstr*> shift_int64_ops_;
- // All CheckArrayBound instructions.
- GrowableArray<CheckArrayBoundInstr*> bounds_checks_;
+ // All CheckArrayBound/GenericCheckBound instructions.
+ GrowableArray<Instruction*> bounds_checks_;
// All Constraints inserted during InsertConstraints phase. They are treated
// as smi values.