8073480: C2 should optimize explicit range checks
authorroland
Tue, 17 Mar 2015 10:06:31 +0100
changeset 30183 a6588c0a3259
parent 29583 acaac5dcf557
child 30184 4454203533c3
8073480: C2 should optimize explicit range checks Summary: explicit range checks should be recognized by C2 Reviewed-by: kvn, vlivanov
hotspot/src/share/vm/oops/methodData.cpp
hotspot/src/share/vm/oops/methodData.hpp
hotspot/src/share/vm/opto/cfgnode.hpp
hotspot/src/share/vm/opto/ifnode.cpp
hotspot/src/share/vm/opto/loopopts.cpp
hotspot/src/share/vm/opto/macro.cpp
hotspot/src/share/vm/opto/multnode.cpp
hotspot/src/share/vm/opto/multnode.hpp
hotspot/src/share/vm/opto/node.cpp
hotspot/src/share/vm/opto/node.hpp
hotspot/src/share/vm/opto/subnode.hpp
hotspot/src/share/vm/runtime/deoptimization.cpp
hotspot/src/share/vm/runtime/deoptimization.hpp
hotspot/src/share/vm/runtime/vmStructs.cpp
hotspot/src/share/vm/utilities/globalDefinitions.hpp
hotspot/test/compiler/rangechecks/TestExplicitRangeChecks.java
--- a/hotspot/src/share/vm/oops/methodData.cpp	Sat Mar 14 16:13:48 2015 +0000
+++ b/hotspot/src/share/vm/oops/methodData.cpp	Tue Mar 17 10:06:31 2015 +0100
@@ -152,6 +152,7 @@
 
 void BitData::print_data_on(outputStream* st, const char* extra) const {
   print_shared(st, "BitData", extra);
+  st->cr();
 }
 
 // ==================================================================
--- a/hotspot/src/share/vm/oops/methodData.hpp	Sat Mar 14 16:13:48 2015 +0000
+++ b/hotspot/src/share/vm/oops/methodData.hpp	Tue Mar 17 10:06:31 2015 +0100
@@ -2056,7 +2056,7 @@
 
   // Whole-method sticky bits and flags
   enum {
-    _trap_hist_limit    = 21,   // decoupled from Deoptimization::Reason_LIMIT
+    _trap_hist_limit    = 22,   // decoupled from Deoptimization::Reason_LIMIT
     _trap_hist_mask     = max_jubyte,
     _extra_data_count   = 4     // extra DataLayout headers, for trap history
   }; // Public flag values
--- a/hotspot/src/share/vm/opto/cfgnode.hpp	Sat Mar 14 16:13:48 2015 +0000
+++ b/hotspot/src/share/vm/opto/cfgnode.hpp	Tue Mar 17 10:06:31 2015 +0100
@@ -263,6 +263,30 @@
   // Size is bigger to hold the probability field.  However, _prob does not
   // change the semantics so it does not appear in the hash & cmp functions.
   virtual uint size_of() const { return sizeof(*this); }
+
+private:
+  ProjNode* range_check_trap_proj(int& flip, Node*& l, Node*& r);
+  ProjNode* range_check_trap_proj() {
+    int flip_test = 0;
+    Node* l = NULL;
+    Node* r = NULL;
+    return range_check_trap_proj(flip_test, l, r);
+  }
+
+  // Helper methods for fold_compares
+  bool cmpi_folds(PhaseIterGVN* igvn);
+  bool is_ctrl_folds(Node* ctrl, PhaseIterGVN* igvn);
+  bool has_shared_region(ProjNode* proj, ProjNode*& success, ProjNode*& fail);
+  bool has_only_uncommon_traps(ProjNode* proj, ProjNode*& success, ProjNode*& fail, PhaseIterGVN* igvn);
+  static void merge_uncommon_traps(ProjNode* proj, ProjNode* success, ProjNode* fail, PhaseIterGVN* igvn);
+  static void improve_address_types(Node* l, Node* r, ProjNode* fail, PhaseIterGVN* igvn);
+  bool is_cmp_with_loadrange(ProjNode* proj);
+  bool is_null_check(ProjNode* proj, PhaseIterGVN* igvn);
+  bool is_side_effect_free_test(ProjNode* proj, PhaseIterGVN* igvn);
+  void reroute_side_effect_free_unc(ProjNode* proj, ProjNode* dom_proj, PhaseIterGVN* igvn);
+  ProjNode* uncommon_trap_proj(CallStaticJavaNode*& call) const;
+  bool fold_compares_helper(ProjNode* proj, ProjNode* success, ProjNode* fail, PhaseIterGVN* igvn);
+
 public:
 
   // Degrees of branch prediction probability by order of magnitude:
@@ -348,7 +372,7 @@
   virtual const RegMask &out_RegMask() const;
   void dominated_by(Node* prev_dom, PhaseIterGVN* igvn);
   int is_range_check(Node* &range, Node* &index, jint &offset);
-  Node* fold_compares(PhaseGVN* phase);
+  Node* fold_compares(PhaseIterGVN* phase);
   static Node* up_one_dom(Node* curr, bool linear_only = false);
 
   // Takes the type of val and filters it through the test represented
--- a/hotspot/src/share/vm/opto/ifnode.cpp	Sat Mar 14 16:13:48 2015 +0000
+++ b/hotspot/src/share/vm/opto/ifnode.cpp	Tue Mar 17 10:06:31 2015 +0100
@@ -25,11 +25,13 @@
 #include "precompiled.hpp"
 #include "memory/allocation.inline.hpp"
 #include "opto/addnode.hpp"
+#include "opto/castnode.hpp"
 #include "opto/cfgnode.hpp"
 #include "opto/connode.hpp"
 #include "opto/loopnode.hpp"
 #include "opto/phaseX.hpp"
 #include "opto/runtime.hpp"
+#include "opto/rootnode.hpp"
 #include "opto/subnode.hpp"
 
 // Portions of code courtesy of Clifford Click
@@ -449,62 +451,59 @@
   return new ConINode(TypeInt::ZERO);
 }
 
-//------------------------------is_range_check---------------------------------
-// Return 0 if not a range check.  Return 1 if a range check and set index and
-// offset.  Return 2 if we had to negate the test.  Index is NULL if the check
-// is versus a constant.
-int IfNode::is_range_check(Node* &range, Node* &index, jint &offset) {
+// if this IfNode follows a range check pattern return the projection
+// for the failed path
+ProjNode* IfNode::range_check_trap_proj(int& flip_test, Node*& l, Node*& r) {
   Node* b = in(1);
-  if (b == NULL || !b->is_Bool())  return 0;
+  if (b == NULL || !b->is_Bool())  return NULL;
   BoolNode* bn = b->as_Bool();
   Node* cmp = bn->in(1);
-  if (cmp == NULL)  return 0;
-  if (cmp->Opcode() != Op_CmpU)  return 0;
+  if (cmp == NULL)  return NULL;
+  if (cmp->Opcode() != Op_CmpU)  return NULL;
 
-  Node* l = cmp->in(1);
-  Node* r = cmp->in(2);
-  int flip_test = 1;
+  l = cmp->in(1);
+  r = cmp->in(2);
+  flip_test = 1;
   if (bn->_test._test == BoolTest::le) {
     l = cmp->in(2);
     r = cmp->in(1);
     flip_test = 2;
   } else if (bn->_test._test != BoolTest::lt) {
-    return 0;
+    return NULL;
   }
-  if (l->is_top())  return 0;   // Top input means dead test
-  if (r->Opcode() != Op_LoadRange)  return 0;
+  if (l->is_top())  return NULL;   // Top input means dead test
+  if (r->Opcode() != Op_LoadRange)  return NULL;
 
   // We have recognized one of these forms:
   //  Flip 1:  If (Bool[<] CmpU(l, LoadRange)) ...
   //  Flip 2:  If (Bool[<=] CmpU(LoadRange, l)) ...
 
+  ProjNode* iftrap = proj_out(flip_test == 2 ? true : false);
+  return iftrap;
+}
+
+
+//------------------------------is_range_check---------------------------------
+// Return 0 if not a range check.  Return 1 if a range check and set index and
+// offset.  Return 2 if we had to negate the test.  Index is NULL if the check
+// is versus a constant.
+int IfNode::is_range_check(Node* &range, Node* &index, jint &offset) {
+  int flip_test = 0;
+  Node* l = NULL;
+  Node* r = NULL;
+  ProjNode* iftrap = range_check_trap_proj(flip_test, l, r);
+
+  if (iftrap == NULL) {
+    return 0;
+  }
+
   // Make sure it's a real range check by requiring an uncommon trap
   // along the OOB path.  Otherwise, it's possible that the user wrote
   // something which optimized to look like a range check but behaves
   // in some other way.
-  Node* iftrap = proj_out(flip_test == 2 ? true : false);
-  bool found_trap = false;
-  if (iftrap != NULL) {
-    Node* u = iftrap->unique_ctrl_out();
-    if (u != NULL) {
-      // It could be a merge point (Region) for uncommon trap.
-      if (u->is_Region()) {
-        Node* c = u->unique_ctrl_out();
-        if (c != NULL) {
-          iftrap = u;
-          u = c;
-        }
-      }
-      if (u->in(0) == iftrap && u->is_CallStaticJava()) {
-        int req = u->as_CallStaticJava()->uncommon_trap_request();
-        if (Deoptimization::trap_request_reason(req) ==
-            Deoptimization::Reason_range_check) {
-          found_trap = true;
-        }
-      }
-    }
+  if (iftrap->is_uncommon_trap_proj(Deoptimization::Reason_range_check) == NULL) {
+    return 0;
   }
-  if (!found_trap)  return 0;   // sorry, no cigar
 
   // Look for index+offset form
   Node* ind = l;
@@ -664,11 +663,12 @@
 //------------------------------fold_compares----------------------------
 // See if a pair of CmpIs can be converted into a CmpU.  In some cases
 // the direction of this if is determined by the preceding if so it
-// can be eliminate entirely.  Given an if testing (CmpI n c) check
-// for an immediately control dependent if that is testing (CmpI n c2)
-// and has one projection leading to this if and the other projection
-// leading to a region that merges one of this ifs control
-// projections.
+// can be eliminate entirely.
+//
+// Given an if testing (CmpI n v) check for an immediately control
+// dependent if that is testing (CmpI n v2) and has one projection
+// leading to this if and the other projection leading to a region
+// that merges one of this ifs control projections.
 //
 //                   If
 //                  / |
@@ -680,79 +680,458 @@
 //            /    \  |
 //           /    Region
 //
-Node* IfNode::fold_compares(PhaseGVN* phase) {
-  if (Opcode() != Op_If) return NULL;
+// Or given an if testing (CmpI n v) check for a dominating if that is
+// testing (CmpI n v2), both having one projection leading to an
+// uncommon trap. Allow Another independent guard in between to cover
+// an explicit range check:
+// if (index < 0 || index >= array.length) {
+// which may need a null check to guard the LoadRange
+//
+//                   If
+//                  / \
+//                 /   \
+//                /     \
+//              If      unc
+//              /\
+//             /  \
+//            /    \
+//           /      unc
+//
+
+// Is the comparison for this If suitable for folding?
+bool IfNode::cmpi_folds(PhaseIterGVN* igvn) {
+  return in(1) != NULL &&
+    in(1)->is_Bool() &&
+    in(1)->in(1) != NULL &&
+    in(1)->in(1)->Opcode() == Op_CmpI &&
+    in(1)->in(1)->in(2) != NULL &&
+    in(1)->in(1)->in(2) != igvn->C->top() &&
+    (in(1)->as_Bool()->_test.is_less() ||
+     in(1)->as_Bool()->_test.is_greater());
+}
+
+// Is a dominating control suitable for folding with this if?
+bool IfNode::is_ctrl_folds(Node* ctrl, PhaseIterGVN* igvn) {
+  return ctrl != NULL &&
+    ctrl->is_Proj() &&
+    ctrl->in(0) != NULL &&
+    ctrl->in(0)->is_If() &&
+    ctrl->in(0)->outcnt() == 2 &&
+    ctrl->in(0)->as_If()->cmpi_folds(igvn) &&
+    // Must compare same value
+    ctrl->in(0)->in(1)->in(1)->in(1) != NULL &&
+    ctrl->in(0)->in(1)->in(1)->in(1) == in(1)->in(1)->in(1);
+}
+
+// Do this If and the dominating If share a region?
+bool IfNode::has_shared_region(ProjNode* proj, ProjNode*& success, ProjNode*& fail) {
+  ProjNode* otherproj = proj->other_if_proj();
+  Node* otherproj_ctrl_use = otherproj->unique_ctrl_out();
+  RegionNode* region = (otherproj_ctrl_use != NULL && otherproj_ctrl_use->is_Region()) ? otherproj_ctrl_use->as_Region() : NULL;
+  success = NULL;
+  fail = NULL;
 
+  if (otherproj->outcnt() == 1 && region != NULL && !region->has_phi()) {
+    for (int i = 0; i < 2; i++) {
+      ProjNode* proj = proj_out(i);
+      if (success == NULL && proj->outcnt() == 1 && proj->unique_out() == region) {
+        success = proj;
+      } else if (fail == NULL) {
+        fail = proj;
+      } else {
+        success = fail = NULL;
+      }
+    }
+  }
+  return success != NULL && fail != NULL;
+}
+
+// Return projection that leads to an uncommon trap if any
+ProjNode* IfNode::uncommon_trap_proj(CallStaticJavaNode*& call) const {
+  for (int i = 0; i < 2; i++) {
+    call = proj_out(i)->is_uncommon_trap_proj(Deoptimization::Reason_none);
+    if (call != NULL) {
+      return proj_out(i);
+    }
+  }
+  return NULL;
+}
+
+// Do this If and the dominating If both branch out to an uncommon trap
+bool IfNode::has_only_uncommon_traps(ProjNode* proj, ProjNode*& success, ProjNode*& fail, PhaseIterGVN* igvn) {
+  ProjNode* otherproj = proj->other_if_proj();
+  CallStaticJavaNode* dom_unc = otherproj->is_uncommon_trap_proj(Deoptimization::Reason_none);
+
+  if (otherproj->outcnt() == 1 && dom_unc != NULL) {
+    CallStaticJavaNode* unc = NULL;
+    ProjNode* unc_proj = uncommon_trap_proj(unc);
+    if (unc_proj != NULL && unc_proj->outcnt() == 1) {
+      if (dom_unc == unc) {
+        // Allow the uncommon trap to be shared through a region
+        RegionNode* r = unc->in(0)->as_Region();
+        if (r->outcnt() != 2 || r->req() != 3 || r->find_edge(otherproj) == -1 || r->find_edge(unc_proj) == -1) {
+          return false;
+        }
+        assert(r->has_phi() == NULL, "simple region shouldn't have a phi");
+      } else if (dom_unc->in(0) != otherproj || unc->in(0) != unc_proj) {
+        return false;
+      }
+      // See merge_uncommon_traps: the reason of the uncommon trap
+      // will be changed and the state of the dominating If will be
+      // used. Checked that we didn't apply this transformation in a
+      // previous compilation and it didn't cause too many traps
+      if (!igvn->C->too_many_traps(dom_unc->jvms()->method(), dom_unc->jvms()->bci(), Deoptimization::Reason_unstable_fused_if) &&
+          !igvn->C->too_many_traps(dom_unc->jvms()->method(), dom_unc->jvms()->bci(), Deoptimization::Reason_range_check)) {
+        success = unc_proj;
+        fail = unc_proj->other_if_proj();
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+// Check that the 2 CmpI can be folded into as single CmpU and proceed with the folding
+bool IfNode::fold_compares_helper(ProjNode* proj, ProjNode* success, ProjNode* fail, PhaseIterGVN* igvn) {
   Node* this_cmp = in(1)->in(1);
-  if (this_cmp != NULL && this_cmp->Opcode() == Op_CmpI &&
-      this_cmp->in(2)->is_Con() && this_cmp->in(2) != phase->C->top()) {
-    Node* ctrl = in(0);
-    BoolNode* this_bool = in(1)->as_Bool();
-    Node* n = this_cmp->in(1);
-    int hi = this_cmp->in(2)->get_int();
-    if (ctrl != NULL && ctrl->is_Proj() && ctrl->outcnt() == 1 &&
-        ctrl->in(0)->is_If() &&
-        ctrl->in(0)->outcnt() == 2 &&
-        ctrl->in(0)->in(1)->is_Bool() &&
-        ctrl->in(0)->in(1)->in(1)->Opcode() == Op_CmpI &&
-        ctrl->in(0)->in(1)->in(1)->in(2)->is_Con() &&
-        ctrl->in(0)->in(1)->in(1)->in(2) != phase->C->top() &&
-        ctrl->in(0)->in(1)->in(1)->in(1) == n) {
-      IfNode* dom_iff = ctrl->in(0)->as_If();
-      Node* otherproj = dom_iff->proj_out(!ctrl->as_Proj()->_con);
-      if (otherproj->outcnt() == 1 && otherproj->unique_out()->is_Region() &&
-          this_bool->_test._test != BoolTest::ne && this_bool->_test._test != BoolTest::eq) {
-        // Identify which proj goes to the region and which continues on
-        RegionNode* region = otherproj->unique_out()->as_Region();
-        Node* success = NULL;
-        Node* fail = NULL;
-        for (int i = 0; i < 2; i++) {
-          Node* proj = proj_out(i);
-          if (success == NULL && proj->outcnt() == 1 && proj->unique_out() == region) {
-            success = proj;
-          } else if (fail == NULL) {
-            fail = proj;
-          } else {
-            success = fail = NULL;
+  BoolNode* this_bool = in(1)->as_Bool();
+  IfNode* dom_iff = proj->in(0)->as_If();
+  BoolNode* dom_bool = dom_iff->in(1)->as_Bool();
+  Node* lo = dom_iff->in(1)->in(1)->in(2);
+  Node* hi = this_cmp->in(2);
+  Node* n = this_cmp->in(1);
+  ProjNode* otherproj = proj->other_if_proj();
+
+  const TypeInt* lo_type = IfNode::filtered_int_type(igvn, n, otherproj);
+  const TypeInt* hi_type = IfNode::filtered_int_type(igvn, n, success);
+
+  BoolTest::mask lo_test = dom_bool->_test._test;
+  BoolTest::mask hi_test = this_bool->_test._test;
+  BoolTest::mask cond = hi_test;
+
+  // Figure out which of the two tests sets the upper bound and which
+  // sets the lower bound if any.
+  if (hi_type->_lo > lo_type->_hi && hi_type->_hi == max_jint && lo_type->_lo == min_jint) {
+
+    assert((dom_bool->_test.is_less() && !proj->_con) ||
+           (dom_bool->_test.is_greater() && proj->_con), "incorrect test");
+    // this test was canonicalized
+    assert(this_bool->_test.is_less() && fail->_con, "incorrect test");
+
+    if (lo_test == BoolTest::gt || lo_test == BoolTest::le) {
+      lo = igvn->transform(new AddINode(lo, igvn->intcon(1)));
+    }
+  } else if (lo_type->_lo > hi_type->_hi && lo_type->_hi == max_jint && hi_type->_lo == min_jint) {
+    swap(lo, hi);
+    swap(lo_type, hi_type);
+    swap(lo_test, hi_test);
+
+    assert((this_bool->_test.is_less() && proj->_con) ||
+           (this_bool->_test.is_greater() && !proj->_con), "incorrect test");
+    // this test was canonicalized
+    assert(dom_bool->_test.is_less() && !fail->_con, "incorrect test");
+
+    cond = (hi_test == BoolTest::le || hi_test == BoolTest::gt) ? BoolTest::gt : BoolTest::ge;
+
+    if (lo_test == BoolTest::le) {
+      lo = igvn->transform(new AddINode(lo, igvn->intcon(1)));
+    }
+
+  } else {
+    const TypeInt* failtype  = filtered_int_type(igvn, n, proj);
+    if (failtype != NULL) {
+      const TypeInt* type2 = filtered_int_type(igvn, n, fail);
+      if (type2 != NULL) {
+        failtype = failtype->join(type2)->is_int();
+        if (failtype->_lo > failtype->_hi) {
+          // previous if determines the result of this if so
+          // replace Bool with constant
+          igvn->hash_delete(this);
+          set_req(1, igvn->intcon(success->_con));
+          return true;
+        }
+      }
+    }
+
+    lo = NULL;
+    hi = NULL;
+  }
+
+  if (lo && hi) {
+    // Merge the two compares into a single unsigned compare by building (CmpU (n - lo) (hi - lo))
+    Node* adjusted_val = igvn->transform(new SubINode(n,  lo));
+    Node* adjusted_lim = igvn->transform(new SubINode(hi, lo));
+    Node* newcmp = igvn->transform(new CmpUNode(adjusted_val, adjusted_lim));
+    Node* newbool = igvn->transform(new BoolNode(newcmp, cond));
+
+    igvn->is_IterGVN()->replace_input_of(dom_iff, 1, igvn->intcon(proj->_con));
+    igvn->hash_delete(this);
+    set_req(1, newbool);
+
+    return true;
+  }
+  return false;
+}
+
+// Merge the branches that trap for this If and the dominating If into
+// a single region that branches to the uncommon trap for the
+// dominating If
+void IfNode::merge_uncommon_traps(ProjNode* proj, ProjNode* success, ProjNode* fail, PhaseIterGVN* igvn) {
+  ProjNode* otherproj = proj->other_if_proj();
+
+  CallStaticJavaNode* unc = success->is_uncommon_trap_proj(Deoptimization::Reason_none);
+  CallStaticJavaNode* dom_unc = otherproj->is_uncommon_trap_proj(Deoptimization::Reason_none);
+
+  if (unc != dom_unc) {
+    Node* r = new RegionNode(3);
+
+    r->set_req(1, otherproj);
+    r->set_req(2, success);
+    r = igvn->transform(r);
+    assert(r->is_Region(), "can't go away");
+
+    // Make both If trap at the state of the first If: once the CmpI
+    // nodes are merged, if we trap we don't know which of the CmpI
+    // nodes would have caused the trap so we have to restart
+    // execution at the first one
+    igvn->replace_input_of(dom_unc, 0, r);
+    igvn->replace_input_of(unc, 0, igvn->C->top());
+  }
+  int trap_request = dom_unc->uncommon_trap_request();
+  Deoptimization::DeoptReason reason = Deoptimization::trap_request_reason(trap_request);
+  Deoptimization::DeoptAction action = Deoptimization::trap_request_action(trap_request);
+
+  int flip_test = 0;
+  Node* l = NULL;
+  Node* r = NULL;
+
+  if (success->in(0)->as_If()->range_check_trap_proj(flip_test, l, r) != NULL) {
+    // If this looks like a range check, change the trap to
+    // Reason_range_check so the compiler recognizes it as a range
+    // check and applies the corresponding optimizations
+    trap_request = Deoptimization::make_trap_request(Deoptimization::Reason_range_check, action);
+
+    improve_address_types(l, r, fail, igvn);
+  } else if (unc != dom_unc) {
+    // If we trap we won't know what CmpI would have caused the trap
+    // so use a special trap reason to mark this pair of CmpI nodes as
+    // bad candidate for folding. On recompilation we won't fold them
+    // and we may trap again but this time we'll know what branch
+    // traps
+    trap_request = Deoptimization::make_trap_request(Deoptimization::Reason_unstable_fused_if, action);
+  }
+  igvn->replace_input_of(dom_unc, TypeFunc::Parms, igvn->intcon(trap_request));
+}
+
+// If we are turning 2 CmpI nodes into a CmpU that follows the pattern
+// of a rangecheck on index i, on 64 bit the compares may be followed
+// by memory accesses using i as index. In that case, the CmpU tells
+// us something about the values taken by i that can help the compiler
+// (see Compile::conv_I2X_index())
+void IfNode::improve_address_types(Node* l, Node* r, ProjNode* fail, PhaseIterGVN* igvn) {
+#ifdef _LP64
+  ResourceMark rm;
+  Node_Stack stack(2);
+
+  assert(r->Opcode() == Op_LoadRange, "unexpected range check");
+  const TypeInt* array_size = igvn->type(r)->is_int();
+
+  stack.push(l, 0);
+
+  while(stack.size() > 0) {
+    Node* n = stack.node();
+    uint start = stack.index();
+
+    uint i = start;
+    for (; i < n->outcnt(); i++) {
+      Node* use = n->raw_out(i);
+      if (stack.size() == 1) {
+        if (use->Opcode() == Op_ConvI2L) {
+          const TypeLong* bounds = use->as_Type()->type()->is_long();
+          if (bounds->_lo <= array_size->_lo && bounds->_hi >= array_size->_hi &&
+              (bounds->_lo != array_size->_lo || bounds->_hi != array_size->_hi)) {
+            stack.set_index(i+1);
+            stack.push(use, 0);
+            break;
           }
         }
-        if (success != NULL && fail != NULL && !region->has_phi()) {
-          int lo = dom_iff->in(1)->in(1)->in(2)->get_int();
-          BoolNode* dom_bool = dom_iff->in(1)->as_Bool();
-          Node* dom_cmp =  dom_bool->in(1);
-          const TypeInt* failtype  = filtered_int_type(phase, n, ctrl);
-          if (failtype != NULL) {
-            const TypeInt* type2 = filtered_int_type(phase, n, fail);
-            if (type2 != NULL) {
-              failtype = failtype->join(type2)->is_int();
-            } else {
-              failtype = NULL;
-            }
-          }
+      } else if (use->is_Mem()) {
+        Node* ctrl = use->in(0);
+        for (int i = 0; i < 10 && ctrl != NULL && ctrl != fail; i++) {
+          ctrl = up_one_dom(ctrl);
+        }
+        if (ctrl == fail) {
+          Node* init_n = stack.node_at(1);
+          assert(init_n->Opcode() == Op_ConvI2L, "unexpected first node");
+          Node* new_n = igvn->C->conv_I2X_index(igvn, l, array_size);
 
-          if (failtype != NULL &&
-              dom_bool->_test._test != BoolTest::ne && dom_bool->_test._test != BoolTest::eq) {
-            int bound = failtype->_hi - failtype->_lo + 1;
-            if (failtype->_hi != max_jint && failtype->_lo != min_jint && bound > 1) {
-              // Merge the two compares into a single unsigned compare by building  (CmpU (n - lo) hi)
-              BoolTest::mask cond = fail->as_Proj()->_con ? BoolTest::lt : BoolTest::ge;
-              Node* adjusted = phase->transform(new SubINode(n, phase->intcon(failtype->_lo)));
-              Node* newcmp = phase->transform(new CmpUNode(adjusted, phase->intcon(bound)));
-              Node* newbool = phase->transform(new BoolNode(newcmp, cond));
-              phase->is_IterGVN()->replace_input_of(dom_iff, 1, phase->intcon(ctrl->as_Proj()->_con));
-              phase->hash_delete(this);
-              set_req(1, newbool);
-              return this;
-            }
-            if (failtype->_lo > failtype->_hi) {
-              // previous if determines the result of this if so
-              // replace Bool with constant
-              phase->hash_delete(this);
-              set_req(1, phase->intcon(success->as_Proj()->_con));
-              return this;
-            }
+          for (uint j = 2; j < stack.size(); j++) {
+            Node* n = stack.node_at(j);
+            Node* clone = n->clone();
+            int rep = clone->replace_edge(init_n, new_n);
+            assert(rep > 0, "can't find expected node?");
+            clone = igvn->transform(clone);
+            init_n = n;
+            new_n = clone;
+          }
+          igvn->hash_delete(use);
+          int rep = use->replace_edge(init_n, new_n);
+          assert(rep > 0, "can't find expected node?");
+          igvn->transform(use);
+          if (init_n->outcnt() == 0) {
+            igvn->_worklist.push(init_n);
           }
         }
+      } else if (use->in(0) == NULL && (igvn->type(use)->isa_long() ||
+                                        igvn->type(use)->isa_ptr())) {
+        stack.set_index(i+1);
+        stack.push(use, 0);
+        break;
+      }
+    }
+    if (i == n->outcnt()) {
+      stack.pop();
+    }
+  }
+#endif
+}
+
+bool IfNode::is_cmp_with_loadrange(ProjNode* proj) {
+  if (in(1) != NULL &&
+      in(1)->in(1) != NULL &&
+      in(1)->in(1)->in(2) != NULL) {
+    Node* other = in(1)->in(1)->in(2);
+    if (other->Opcode() == Op_LoadRange &&
+        ((other->in(0) != NULL && other->in(0) == proj) ||
+         (other->in(0) == NULL &&
+          other->in(2) != NULL &&
+          other->in(2)->is_AddP() &&
+          other->in(2)->in(1) != NULL &&
+          other->in(2)->in(1)->Opcode() == Op_CastPP &&
+          other->in(2)->in(1)->in(0) == proj))) {
+      return true;
+    }
+  }
+  return false;
+}
+
+bool IfNode::is_null_check(ProjNode* proj, PhaseIterGVN* igvn) {
+  Node* other = in(1)->in(1)->in(2);
+  if (other->in(MemNode::Address) != NULL &&
+      proj->in(0)->in(1) != NULL &&
+      proj->in(0)->in(1)->is_Bool() &&
+      proj->in(0)->in(1)->in(1) != NULL &&
+      proj->in(0)->in(1)->in(1)->Opcode() == Op_CmpP &&
+      proj->in(0)->in(1)->in(1)->in(2) != NULL &&
+      proj->in(0)->in(1)->in(1)->in(1) == other->in(MemNode::Address)->in(AddPNode::Address)->uncast() &&
+      igvn->type(proj->in(0)->in(1)->in(1)->in(2)) == TypePtr::NULL_PTR) {
+    return true;
+  }
+  return false;
+}
+
+// Check that the If that is in between the 2 integer comparisons has
+// no side effect
+bool IfNode::is_side_effect_free_test(ProjNode* proj, PhaseIterGVN* igvn) {
+  if (proj != NULL &&
+      proj->is_uncommon_trap_if_pattern(Deoptimization::Reason_none) &&
+      proj->outcnt() <= 2) {
+    if (proj->outcnt() == 1 ||
+        // Allow simple null check from LoadRange
+        (is_cmp_with_loadrange(proj) && is_null_check(proj, igvn))) {
+      CallStaticJavaNode* unc = proj->is_uncommon_trap_if_pattern(Deoptimization::Reason_none);
+      CallStaticJavaNode* dom_unc = proj->in(0)->in(0)->as_Proj()->is_uncommon_trap_if_pattern(Deoptimization::Reason_none);
+
+      // reroute_side_effect_free_unc changes the state of this
+      // uncommon trap to restart execution at the previous
+      // CmpI. Check that this change in a previous compilation didn't
+      // cause too many traps.
+      int trap_request = unc->uncommon_trap_request();
+      Deoptimization::DeoptReason reason = Deoptimization::trap_request_reason(trap_request);
+
+      if (igvn->C->too_many_traps(dom_unc->jvms()->method(), dom_unc->jvms()->bci(), reason)) {
+        return false;
+      }
+
+      return true;
+    }
+  }
+  return false;
+}
+
+// Make the If between the 2 integer comparisons trap at the state of
+// the first If: the last CmpI is the one replaced by a CmpU and the
+// first CmpI is eliminated, so the test between the 2 CmpI nodes
+// won't be guarded by the first CmpI anymore. It can trap in cases
+// where the first CmpI would have prevented it from executing: on a
+// trap, we need to restart execution at the state of the first CmpI
+void IfNode::reroute_side_effect_free_unc(ProjNode* proj, ProjNode* dom_proj, PhaseIterGVN* igvn) {
+  CallStaticJavaNode* dom_unc = dom_proj->is_uncommon_trap_if_pattern(Deoptimization::Reason_none);
+  ProjNode* otherproj = proj->other_if_proj();
+  CallStaticJavaNode* unc = proj->is_uncommon_trap_if_pattern(Deoptimization::Reason_none);
+  Node* call_proj = dom_unc->unique_ctrl_out();
+  Node* halt = call_proj->unique_ctrl_out();
+
+  Node* new_unc = dom_unc->clone();
+  call_proj = call_proj->clone();
+  halt = halt->clone();
+  Node* c = otherproj->clone();
+
+  c = igvn->transform(c);
+  new_unc->set_req(TypeFunc::Parms, unc->in(TypeFunc::Parms));
+  new_unc->set_req(0, c);
+  new_unc = igvn->transform(new_unc);
+  call_proj->set_req(0, new_unc);
+  call_proj = igvn->transform(call_proj);
+  halt->set_req(0, call_proj);
+  halt = igvn->transform(halt);
+
+  igvn->replace_node(otherproj, igvn->C->top());
+  igvn->C->root()->add_req(halt);
+}
+
+Node* IfNode::fold_compares(PhaseIterGVN* igvn) {
+  if (Opcode() != Op_If) return NULL;
+
+  if (cmpi_folds(igvn)) {
+    Node* ctrl = in(0);
+    if (is_ctrl_folds(ctrl, igvn) &&
+        ctrl->outcnt() == 1) {
+      // A integer comparison immediately dominated by another integer
+      // comparison
+      ProjNode* success = NULL;
+      ProjNode* fail = NULL;
+      ProjNode* dom_cmp = ctrl->as_Proj();
+      if (has_shared_region(dom_cmp, success, fail) &&
+          // Next call modifies graph so must be last
+          fold_compares_helper(dom_cmp, success, fail, igvn)) {
+        return this;
+      }
+      if (has_only_uncommon_traps(dom_cmp, success, fail, igvn) &&
+          // Next call modifies graph so must be last
+          fold_compares_helper(dom_cmp, success, fail, igvn)) {
+        merge_uncommon_traps(dom_cmp, success, fail, igvn);
+        return this;
+      }
+      return NULL;
+    } else if (ctrl->in(0) != NULL &&
+               ctrl->in(0)->in(0) != NULL) {
+      ProjNode* success = NULL;
+      ProjNode* fail = NULL;
+      Node* dom = ctrl->in(0)->in(0);
+      ProjNode* dom_cmp = dom->isa_Proj();
+      ProjNode* other_cmp = ctrl->isa_Proj();
+
+      // Check if it's an integer comparison dominated by another
+      // integer comparison with another test in between
+      if (is_ctrl_folds(dom, igvn) &&
+          has_only_uncommon_traps(dom_cmp, success, fail, igvn) &&
+          is_side_effect_free_test(other_cmp, igvn) &&
+          // Next call modifies graph so must be last
+          fold_compares_helper(dom_cmp, success, fail, igvn)) {
+        reroute_side_effect_free_unc(other_cmp, dom_cmp, igvn);
+        merge_uncommon_traps(dom_cmp, success, fail, igvn);
+        return this;
       }
     }
   }
@@ -1029,7 +1408,7 @@
     // Normal equivalent-test check.
     if( !dom ) return NULL;     // Dead loop?
 
-    Node* result = fold_compares(phase);
+    Node* result = fold_compares(igvn);
     if (result != NULL) {
       return result;
     }
@@ -1089,7 +1468,7 @@
   // be skipped. For example, range check predicate has two checks
   // for lower and upper bounds.
   ProjNode* unc_proj = proj_out(1 - prev_dom->as_Proj()->_con)->as_Proj();
-  if (unc_proj->is_uncommon_trap_proj(Deoptimization::Reason_predicate))
+  if (unc_proj->is_uncommon_trap_proj(Deoptimization::Reason_predicate) != NULL)
    prev_dom = idom;
 
   // Now walk the current IfNode's projections.
--- a/hotspot/src/share/vm/opto/loopopts.cpp	Sat Mar 14 16:13:48 2015 +0000
+++ b/hotspot/src/share/vm/opto/loopopts.cpp	Tue Mar 17 10:06:31 2015 +0100
@@ -241,8 +241,8 @@
   ProjNode* dp_proj  = dp->as_Proj();
   ProjNode* unc_proj = iff->as_If()->proj_out(1 - dp_proj->_con)->as_Proj();
   if (exclude_loop_predicate &&
-      (unc_proj->is_uncommon_trap_proj(Deoptimization::Reason_predicate) ||
-       unc_proj->is_uncommon_trap_proj(Deoptimization::Reason_range_check))) {
+      (unc_proj->is_uncommon_trap_proj(Deoptimization::Reason_predicate) != NULL ||
+       unc_proj->is_uncommon_trap_proj(Deoptimization::Reason_range_check) != NULL)) {
     // If this is a range check (IfNode::is_range_check), do not
     // reorder because Compile::allow_range_check_smearing might have
     // changed the check.
--- a/hotspot/src/share/vm/opto/macro.cpp	Sat Mar 14 16:13:48 2015 +0000
+++ b/hotspot/src/share/vm/opto/macro.cpp	Tue Mar 17 10:06:31 2015 +0100
@@ -2535,7 +2535,7 @@
                (bol->_test._test == BoolTest::ne), "");
         IfNode* ifn = bol->unique_out()->as_If();
         assert((ifn->outcnt() == 2) &&
-               ifn->proj_out(1)->is_uncommon_trap_proj(Deoptimization::Reason_rtm_state_change), "");
+               ifn->proj_out(1)->is_uncommon_trap_proj(Deoptimization::Reason_rtm_state_change) != NULL, "");
 #endif
         Node* repl = n->in(1);
         if (!_has_locks) {
--- a/hotspot/src/share/vm/opto/multnode.cpp	Sat Mar 14 16:13:48 2015 +0000
+++ b/hotspot/src/share/vm/opto/multnode.cpp	Tue Mar 17 10:06:31 2015 +0100
@@ -150,59 +150,67 @@
 }
 
 //-------------------------------is_uncommon_trap_proj----------------------------
-// Return true if proj is the form of "proj->[region->..]call_uct"
-bool ProjNode::is_uncommon_trap_proj(Deoptimization::DeoptReason reason) {
+// Return uncommon trap call node if proj is for "proj->[region->..]call_uct"
+// NULL otherwise
+CallStaticJavaNode* ProjNode::is_uncommon_trap_proj(Deoptimization::DeoptReason reason) {
   int path_limit = 10;
   Node* out = this;
   for (int ct = 0; ct < path_limit; ct++) {
     out = out->unique_ctrl_out();
     if (out == NULL)
-      return false;
+      return NULL;
     if (out->is_CallStaticJava()) {
-      int req = out->as_CallStaticJava()->uncommon_trap_request();
+      CallStaticJavaNode* call = out->as_CallStaticJava();
+      int req = call->uncommon_trap_request();
       if (req != 0) {
         Deoptimization::DeoptReason trap_reason = Deoptimization::trap_request_reason(req);
         if (trap_reason == reason || reason == Deoptimization::Reason_none) {
-           return true;
+          return call;
         }
       }
-      return false; // don't do further after call
+      return NULL; // don't do further after call
     }
     if (out->Opcode() != Op_Region)
-      return false;
+      return NULL;
   }
-  return false;
+  return NULL;
 }
 
 //-------------------------------is_uncommon_trap_if_pattern-------------------------
-// Return true  for "if(test)-> proj -> ...
-//                          |
-//                          V
-//                      other_proj->[region->..]call_uct"
-//
+// Return uncommon trap call node for    "if(test)-> proj -> ...
+//                                                 |
+//                                                 V
+//                                             other_proj->[region->..]call_uct"
+// NULL otherwise
 // "must_reason_predicate" means the uct reason must be Reason_predicate
-bool ProjNode::is_uncommon_trap_if_pattern(Deoptimization::DeoptReason reason) {
+CallStaticJavaNode* ProjNode::is_uncommon_trap_if_pattern(Deoptimization::DeoptReason reason) {
   Node *in0 = in(0);
-  if (!in0->is_If()) return false;
+  if (!in0->is_If()) return NULL;
   // Variation of a dead If node.
-  if (in0->outcnt() < 2)  return false;
+  if (in0->outcnt() < 2)  return NULL;
   IfNode* iff = in0->as_If();
 
   // we need "If(Conv2B(Opaque1(...)))" pattern for reason_predicate
   if (reason != Deoptimization::Reason_none) {
     if (iff->in(1)->Opcode() != Op_Conv2B ||
        iff->in(1)->in(1)->Opcode() != Op_Opaque1) {
-      return false;
+      return NULL;
     }
   }
 
   ProjNode* other_proj = iff->proj_out(1-_con);
   if (other_proj == NULL) // Should never happen, but make Parfait happy.
-      return false;
-  if (other_proj->is_uncommon_trap_proj(reason)) {
+      return NULL;
+  CallStaticJavaNode* call = other_proj->is_uncommon_trap_proj(reason);
+  if (call != NULL) {
     assert(reason == Deoptimization::Reason_none ||
            Compile::current()->is_predicate_opaq(iff->in(1)->in(1)), "should be on the list");
-    return true;
+    return call;
   }
-  return false;
+  return NULL;
 }
+
+ProjNode* ProjNode::other_if_proj() const {
+  assert(_con == 0 || _con == 1, "not an if?");
+  return in(0)->as_If()->proj_out(1-_con);
+}
--- a/hotspot/src/share/vm/opto/multnode.hpp	Sat Mar 14 16:13:48 2015 +0000
+++ b/hotspot/src/share/vm/opto/multnode.hpp	Tue Mar 17 10:06:31 2015 +0100
@@ -89,13 +89,18 @@
   virtual void dump_spec(outputStream *st) const;
 #endif
 
-  // Return true if proj is for "proj->[region->..]call_uct"
-  bool is_uncommon_trap_proj(Deoptimization::DeoptReason reason);
-  // Return true for    "if(test)-> proj -> ...
-  //                          |
-  //                          V
-  //                      other_proj->[region->..]call_uct"
-  bool is_uncommon_trap_if_pattern(Deoptimization::DeoptReason reason);
+  // Return uncommon trap call node if proj is for "proj->[region->..]call_uct"
+  // NULL otherwise
+  CallStaticJavaNode* is_uncommon_trap_proj(Deoptimization::DeoptReason reason);
+  // Return uncommon trap call node for    "if(test)-> proj -> ...
+  //                                                 |
+  //                                                 V
+  //                                             other_proj->[region->..]call_uct"
+  // NULL otherwise
+  CallStaticJavaNode* is_uncommon_trap_if_pattern(Deoptimization::DeoptReason reason);
+
+  // Return other proj node when this is a If proj node
+  ProjNode* other_if_proj() const;
 };
 
 #endif // SHARE_VM_OPTO_MULTNODE_HPP
--- a/hotspot/src/share/vm/opto/node.cpp	Sat Mar 14 16:13:48 2015 +0000
+++ b/hotspot/src/share/vm/opto/node.cpp	Tue Mar 17 10:06:31 2015 +0100
@@ -2069,7 +2069,7 @@
 
 //--------------------------unique_ctrl_out------------------------------
 // Return the unique control out if only one. Null if none or more than one.
-Node* Node::unique_ctrl_out() {
+Node* Node::unique_ctrl_out() const {
   Node* found = NULL;
   for (uint i = 0; i < outcnt(); i++) {
     Node* use = raw_out(i);
--- a/hotspot/src/share/vm/opto/node.hpp	Sat Mar 14 16:13:48 2015 +0000
+++ b/hotspot/src/share/vm/opto/node.hpp	Tue Mar 17 10:06:31 2015 +0100
@@ -931,7 +931,7 @@
   Node* find_similar(int opc);
 
   // Return the unique control out if only one. Null if none or more than one.
-  Node* unique_ctrl_out();
+  Node* unique_ctrl_out() const;
 
 //----------------- Code Generation
 
--- a/hotspot/src/share/vm/opto/subnode.hpp	Sat Mar 14 16:13:48 2015 +0000
+++ b/hotspot/src/share/vm/opto/subnode.hpp	Tue Mar 17 10:06:31 2015 +0100
@@ -275,6 +275,8 @@
   mask commute( ) const { return mask("032147658"[_test]-'0'); }
   mask negate( ) const { return mask(_test^4); }
   bool is_canonical( ) const { return (_test == BoolTest::ne || _test == BoolTest::lt || _test == BoolTest::le || _test == BoolTest::overflow); }
+  bool is_less( )  const { return _test == BoolTest::lt || _test == BoolTest::le; }
+  bool is_greater( ) const { return _test == BoolTest::gt || _test == BoolTest::ge; }
   void dump_on(outputStream *st) const;
 };
 
--- a/hotspot/src/share/vm/runtime/deoptimization.cpp	Sat Mar 14 16:13:48 2015 +0000
+++ b/hotspot/src/share/vm/runtime/deoptimization.cpp	Tue Mar 17 10:06:31 2015 +0100
@@ -1861,6 +1861,7 @@
   "speculate_null_check",
   "rtm_state_change",
   "unstable_if",
+  "unstable_fused_if",
   "tenured"
 };
 const char* Deoptimization::_trap_action_name[] = {
--- a/hotspot/src/share/vm/runtime/deoptimization.hpp	Sat Mar 14 16:13:48 2015 +0000
+++ b/hotspot/src/share/vm/runtime/deoptimization.hpp	Tue Mar 17 10:06:31 2015 +0100
@@ -63,6 +63,7 @@
     Reason_speculate_null_check,  // saw unexpected null from type speculation
     Reason_rtm_state_change,      // rtm state change detected
     Reason_unstable_if,           // a branch predicted always false was taken
+    Reason_unstable_fused_if,     // fused two ifs that had each one untaken branch. One is now taken.
 
     // Reason_tenured is counted separately, add normal counted Reasons above.
     // Related to MethodData::_trap_hist_limit where Reason_tenured isn't included
@@ -326,6 +327,8 @@
       return Reason_null_check;
     else if (reason == Reason_unstable_if)
       return Reason_intrinsic;
+    else if (reason == Reason_unstable_fused_if)
+      return Reason_range_check;
     else
       return Reason_none;
   }
--- a/hotspot/src/share/vm/runtime/vmStructs.cpp	Sat Mar 14 16:13:48 2015 +0000
+++ b/hotspot/src/share/vm/runtime/vmStructs.cpp	Tue Mar 17 10:06:31 2015 +0100
@@ -2513,6 +2513,7 @@
   declare_constant(Deoptimization::Reason_speculate_null_check)           \
   declare_constant(Deoptimization::Reason_rtm_state_change)               \
   declare_constant(Deoptimization::Reason_unstable_if)                    \
+  declare_constant(Deoptimization::Reason_unstable_fused_if)              \
   declare_constant(Deoptimization::Reason_tenured)                        \
   declare_constant(Deoptimization::Reason_LIMIT)                          \
   declare_constant(Deoptimization::Reason_RECORDED_LIMIT)                 \
--- a/hotspot/src/share/vm/utilities/globalDefinitions.hpp	Sat Mar 14 16:13:48 2015 +0000
+++ b/hotspot/src/share/vm/utilities/globalDefinitions.hpp	Tue Mar 17 10:06:31 2015 +0100
@@ -1354,6 +1354,13 @@
   return (intptr_t) p;
 }
 
+// swap a & b
+template<class T> static void swap(T& a, T& b) {
+  T tmp = a;
+  a = b;
+  b = tmp;
+}
+
 // Printf-style formatters for fixed- and variable-width types as pointers and
 // integers.  These are derived from the definitions in inttypes.h.  If the platform
 // doesn't provide appropriate definitions, they should be provided in
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/test/compiler/rangechecks/TestExplicitRangeChecks.java	Tue Mar 17 10:06:31 2015 +0100
@@ -0,0 +1,596 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8073480
+ * @summary explicit range checks should be recognized by C2
+ * @library /testlibrary /../../test/lib /compiler/whitebox
+ * @build  TestExplicitRangeChecks
+ * @run main ClassFileInstaller sun.hotspot.WhiteBox
+ * @run main ClassFileInstaller com.oracle.java.testlibrary.Platform
+ * @run main/othervm -ea -Xmixed -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI
+ *                   -XX:-BackgroundCompilation -XX:-UseOnStackReplacement -XX:CompileCommand=compileonly,TestExplicitRangeChecks.test* TestExplicitRangeChecks
+ *
+ */
+
+import java.lang.annotation.*;
+import java.lang.reflect.*;
+import java.util.*;
+import sun.hotspot.WhiteBox;
+import sun.hotspot.code.NMethod;
+import com.oracle.java.testlibrary.Platform;
+import sun.misc.Unsafe;
+
+public class TestExplicitRangeChecks {
+
+    static int[] array = new int[10];
+
+    @Retention(RetentionPolicy.RUNTIME)
+    @interface Args {
+        int[] compile();
+        int[] good();
+        int[] bad();
+        boolean deoptimize() default true;
+    }
+
+    // Should be compiled as a single unsigned comparison
+    // 0 <= index < array.length
+    @Args(compile = {5,}, good = {0, 9}, bad = {-1, 10})
+    static boolean test1_1(int index, int[] array) {
+        if (index < 0 || index >= array.length) {
+            return false;
+        }
+        return true;
+    }
+
+    // same test but so we can compile with same optimization after trap in test1_1
+    static boolean test1_2(int index, int[] array) {
+        if (index < 0 || index >= array.length) {
+            return false;
+        }
+        return true;
+    }
+
+    // Shouldn't matter whether first or second test is the one
+    // against a constants
+    // 0 <= index < array.length
+    @Args(compile = {5,}, good = {0, 9}, bad = {-1, 10})
+    static boolean test2_1(int index, int[] array) {
+        if (index >= array.length || index < 0) {
+            return false;
+        }
+        return true;
+    }
+
+    static boolean test2_2(int index, int[] array) {
+        if (index >= array.length || index < 0) {
+            return false;
+        }
+        return true;
+    }
+
+    // 0 <= index <= array.length
+    @Args(compile = {5,}, good = {0, 10}, bad = {-1, 11})
+    static boolean test3_1(int index, int[] array) {
+        if (index < 0 || index > array.length) {
+            return false;
+        }
+        return true;
+    }
+
+    static boolean test3_2(int index, int[] array) {
+        if (index < 0 || index > array.length) {
+            return false;
+        }
+        return true;
+    }
+
+    // 0 <= index <= array.length
+    @Args(compile = {5,}, good = {0, 10}, bad = {-1, 11})
+    static boolean test4_1(int index, int[] array) {
+        if (index > array.length || index < 0 ) {
+            return false;
+        }
+        return true;
+    }
+
+    static boolean test4_2(int index, int[] array) {
+        if (index > array.length || index < 0) {
+            return false;
+        }
+        return true;
+    }
+
+    static int[] test5_helper(int i) {
+        return (i < 100) ? new int[10] : new int[5];
+    }
+
+    // 0 < index < array.length
+    @Args(compile = {5,}, good = {1, 9}, bad = {0, 10})
+    static boolean test5_1(int index, int[] array) {
+        array = test5_helper(index); // array.length must be not constant greater than 1
+        if (index <= 0 || index >= array.length) {
+            return false;
+        }
+        return true;
+    }
+
+    static boolean test5_2(int index, int[] array) {
+        array = test5_helper(index); // array.length must be not constant greater than 1
+        if (index <= 0 || index >= array.length) {
+            return false;
+        }
+        return true;
+    }
+
+    // 0 < index < array.length
+    @Args(compile = {5,}, good = {1, 9}, bad = {0, 10})
+    static boolean test6_1(int index, int[] array) {
+        array = test5_helper(index); // array.length must be not constant greater than 1
+        if (index >= array.length || index <= 0 ) {
+            return false;
+        }
+        return true;
+    }
+
+    static boolean test6_2(int index, int[] array) {
+        array = test5_helper(index); // array.length must be not constant greater than 1
+        if (index >= array.length || index <= 0) {
+            return false;
+        }
+        return true;
+    }
+
+    // 0 < index <= array.length
+    @Args(compile = {5,}, good = {1, 10}, bad = {0, 11})
+    static boolean test7_1(int index, int[] array) {
+        if (index <= 0 || index > array.length) {
+            return false;
+        }
+        return true;
+    }
+
+    static boolean test7_2(int index, int[] array) {
+        if (index <= 0 || index > array.length) {
+            return false;
+        }
+        return true;
+    }
+
+    // 0 < index <= array.length
+    @Args(compile = {5,}, good = {1, 10}, bad = {0, 11})
+    static boolean test8_1(int index, int[] array) {
+        if (index > array.length || index <= 0 ) {
+            return false;
+        }
+        return true;
+    }
+
+    static boolean test8_2(int index, int[] array) {
+        if (index > array.length || index <= 0) {
+            return false;
+        }
+        return true;
+    }
+
+    static int[] test9_helper1(int i) {
+        return (i < 100) ? new int[1] : new int[2];
+    }
+
+    static int[] test9_helper2(int i) {
+        return (i < 100) ? new int[10] : new int[11];
+    }
+
+    // array1.length <= index < array2.length
+    @Args(compile = {5,}, good = {1, 9}, bad = {0, 10})
+    static boolean test9_1(int index, int[] array) {
+        int[] array1 = test9_helper1(index);
+        int[] array2 = test9_helper2(index);
+        if (index < array1.length || index >= array2.length) {
+            return false;
+        }
+        return true;
+    }
+
+    static boolean test9_2(int index, int[] array) {
+        int[] array1 = test9_helper1(index);
+        int[] array2 = test9_helper2(index);
+        if (index < array1.length || index >= array2.length) {
+            return false;
+        }
+        return true;
+    }
+
+    // Previously supported pattern
+    @Args(compile = {-5,5,15}, good = {0, 9}, bad = {-1, 10}, deoptimize=false)
+    static boolean test10_1(int index, int[] array) {
+        if (index < 0 || index >= 10) {
+            return false;
+        }
+        return true;
+    }
+
+    static int[] array11 = new int[10];
+    @Args(compile = {5,}, good = {0, 9}, bad = {-1,})
+    static boolean test11_1(int index, int[] array) {
+        if (index < 0) {
+            return false;
+        }
+        int unused = array11[index];
+        // If this one is folded with the first test then we allow
+        // array access above to proceed even for out of bound array
+        // index and the method throws an
+        // ArrayIndexOutOfBoundsException.
+        if (index >= array.length) {
+            return false;
+        }
+        return true;
+    }
+
+    static int[] array12 = {10, 10, 10, 10, 10, 10, 10, 10, 10, 10};
+    @Args(compile = {5,}, good = {0, 9}, bad = {-1,})
+    static boolean test12_1(int index, int[] array) {
+        // Cannot be folded otherwise would cause incorrect array
+        // access if the array12 range check is executed before the
+        // folded test.
+        if (index < 0 || index >= array12[index]) {
+            return false;
+        }
+        return true;
+    }
+
+    // Same as test1_1 but pass null array when index < 0: shouldn't
+    // cause NPE.
+    @Args(compile = {5,}, good = {0, 9}, bad = {})
+    static boolean test13_1(int index, int[] array) {
+        if (index < 0 || index >= array.length) {
+            return false;
+        }
+        return true;
+    }
+
+    // Same as test10 but with uncommon traps
+    @Args(compile = {5}, good = {0, 9}, bad = {-1, 10})
+    static boolean test14_1(int index, int[] array) {
+        if (index < 0 || index >= 10) {
+            return false;
+        }
+        return true;
+    }
+
+    static boolean test14_2(int index, int[] array) {
+        if (index < 0 || index >= 10) {
+            return false;
+        }
+        return true;
+    }
+
+    // Same as test13_1 but pass null array: null trap should be reported on first if
+    @Args(compile = {5,}, good = {0, 9}, bad = {})
+    static boolean test15_1(int index, int[] array) {
+        if (index < 0 || index >= array.length) {
+            return false;
+        }
+        return true;
+    }
+
+    // Same as test1 but with no null check between the integer comparisons
+    @Args(compile = {5,}, good = {0, 9}, bad = {-1, 10})
+    static boolean test16_1(int index, int[] array) {
+        int l = array.length;
+        if (index < 0 || index >= l) {
+            return false;
+        }
+        return true;
+    }
+
+    static boolean test16_2(int index, int[] array) {
+        int l = array.length;
+        if (index < 0 || index >= l) {
+            return false;
+        }
+        return true;
+    }
+
+    // Same as test1 but bound check on array access should optimize
+    // out.
+    @Args(compile = {5,}, good = {0, 9}, bad = {-1, 10})
+    static boolean test17_1(int index, int[] array) {
+        if (index < 0 || index >= array.length) {
+            return false;
+        }
+        array[index] = 0;
+        return true;
+    }
+
+    static boolean test17_2(int index, int[] array) {
+        if (index < 0 || index >= array.length) {
+            return false;
+        }
+        array[index] = 0;
+        return true;
+    }
+
+    // Same as test1 but range check smearing should optimize
+    // 3rd range check out.
+    @Args(compile = {5,}, good = {}, bad = {})
+    static boolean test18_1(int index, int[] array) {
+        if (index < 0 || index >= array.length) {
+            return false;
+        }
+        array[index+2] = 0;
+        array[index+1] = 0;
+        return true;
+    }
+
+    static boolean test19_helper1(int index) {
+        if (index < 12) {
+            return false;
+        }
+        return true;
+    }
+
+    static boolean test19_helper2(int index) {
+        if (index > 8) {
+            return false;
+        }
+        return true;
+    }
+
+    // Second test should be optimized out
+    static boolean test19(int index, int[] array) {
+        test19_helper1(index);
+        test19_helper2(index);
+        return true;
+    }
+
+    static boolean success = true;
+
+    private static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox();
+
+    final HashMap<String,Method> tests = new HashMap<>();
+    {
+        for (Method m : this.getClass().getDeclaredMethods()) {
+            if (m.getName().matches("test[0-9]+(_[0-9])?")) {
+                assert(Modifier.isStatic(m.getModifiers())) : m;
+                tests.put(m.getName(), m);
+            }
+        }
+    }
+
+    void doTest(String name) throws Exception {
+        Method m = tests.get(name + "_1");
+
+        Args anno =  m.getAnnotation(Args.class);
+        int[] compile = anno.compile();
+        int[] good = anno.good();
+        int[] bad = anno.bad();
+        boolean deoptimize = anno.deoptimize();
+
+        // Get compiled
+        for (int i = 0; i < 20000;) {
+            for (int j = 0; j < compile.length; j++) {
+                m.invoke(null, compile[j], array);
+                i++;
+            }
+        }
+
+        if (!WHITE_BOX.isMethodCompiled(m)) {
+            System.out.println(name + "_1 not compiled");
+            success = false;
+        }
+
+        // check that good values don't trigger exception or
+        // deoptimization
+        for (int i = 0; i < good.length; i++) {
+            boolean res = (boolean)m.invoke(null, good[i], array);
+
+            if (!res) {
+                System.out.println(name + " bad result for good input " + good[i]);
+                success = false;
+            }
+            if (!WHITE_BOX.isMethodCompiled(m)) {
+                System.out.println(name + " deoptimized on valid access");
+                success = false;
+            }
+        }
+
+        // check that bad values trigger exception and deoptimization
+        for (int i = 0; i < bad.length; i++) {
+            if (i > 0 && deoptimize) {
+                m = tests.get(name + "_" + (i+1));
+                for (int k = 0; k < 20000;) {
+                    for (int j = 0; j < compile.length; j++) {
+                        m.invoke(null, compile[j], array);
+                        k++;
+                    }
+                }
+                if (!WHITE_BOX.isMethodCompiled(m)) {
+                    System.out.println(name + ("_" + (i+1)) + " not compiled");
+                    success = false;
+                }
+            }
+
+            boolean res = (boolean)m.invoke(null, bad[i], array);
+
+            if (res) {
+                System.out.println(name + " bad result for bad input " + bad[i]);
+                success = false;
+            }
+            if (Platform.isServer()) {
+                if (deoptimize && WHITE_BOX.isMethodCompiled(m)) {
+                    System.out.println(name + " not deoptimized on invalid access");
+                    success = false;
+                } else if (!deoptimize && !WHITE_BOX.isMethodCompiled(m)) {
+                    System.out.println(name + " deoptimized on invalid access");
+                    success = false;
+                }
+            }
+        }
+
+    }
+
+    private static final Unsafe UNSAFE;
+
+    static {
+        try {
+            Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
+            unsafeField.setAccessible(true);
+            UNSAFE = (Unsafe) unsafeField.get(null);
+        }
+        catch (Exception e) {
+            throw new AssertionError(e);
+        }
+    }
+
+    // On x64, int to long conversion should optimize away in address computation
+    static int test20(int[] a) {
+        int sum = 0;
+        for (int i = 0; i < a.length; i++) {
+            sum += test20_helper(a, i);
+        }
+        return sum;
+    }
+
+    static int test20_helper(int[] a, int i) {
+        if (i < 0 || i >= a.length)
+            throw new ArrayIndexOutOfBoundsException();
+
+        long address = (((long) i) << 2) + UNSAFE.ARRAY_INT_BASE_OFFSET;
+        return UNSAFE.getInt(a, address);
+    }
+
+    static int test21(int[] a) {
+        int sum = 0;
+        for (int i = 0; i < a.length; i++) {
+            sum += test20_helper(a, i);
+        }
+        return sum;
+    }
+
+    static int test21_helper(int[] a, int i) {
+        if (i < 0 || i >= a.length)
+            throw new ArrayIndexOutOfBoundsException();
+
+        long address = (((long) i) << 2) + UNSAFE.ARRAY_INT_BASE_OFFSET;
+        return UNSAFE.getIntVolatile(a, address);
+    }
+
+    static public void main(String[] args) throws Exception {
+
+        if (WHITE_BOX.getBooleanVMFlag("BackgroundCompilation")) {
+            throw new AssertionError("Background compilation enabled");
+        }
+
+        TestExplicitRangeChecks test = new TestExplicitRangeChecks();
+
+        test.doTest("test1");
+        test.doTest("test2");
+        test.doTest("test3");
+        test.doTest("test4");
+
+        // pollute branch profile
+        for (int i = 0; i < 10000; i++) {
+            test5_helper((i%2 == 0) ? 0 : 1000);
+        }
+
+        test.doTest("test5");
+        test.doTest("test6");
+        test.doTest("test7");
+        test.doTest("test8");
+
+        // pollute branch profile
+        for (int i = 0; i < 10000; i++) {
+            test9_helper1((i%2 == 0) ? 0 : 1000);
+            test9_helper2((i%2 == 0) ? 0 : 1000);
+        }
+
+        test.doTest("test9");
+        test.doTest("test10");
+        test.doTest("test11");
+        test.doTest("test12");
+
+        test.doTest("test13");
+        {
+            Method m = test.tests.get("test13_1");
+            for (int i = 0; i < 1; i++) {
+                test13_1(-1, null);
+                if (!WHITE_BOX.isMethodCompiled(m)) {
+                    break;
+                }
+            }
+        }
+        test.doTest("test13");
+        {
+            Method m = test.tests.get("test13_1");
+            for (int i = 0; i < 10; i++) {
+                test13_1(-1, null);
+                if (!WHITE_BOX.isMethodCompiled(m)) {
+                    break;
+                }
+            }
+        }
+
+        test.doTest("test14");
+
+        test.doTest("test15");
+        {
+            Method m = test.tests.get("test15_1");
+            for (int i = 0; i < 10; i++) {
+                try {
+                    test15_1(5, null);
+                } catch(NullPointerException npe) {}
+                if (!WHITE_BOX.isMethodCompiled(m)) {
+                    break;
+                }
+            }
+        }
+        test.doTest("test15");
+        test.doTest("test16");
+        test.doTest("test17");
+        test.doTest("test18");
+
+        for (int i = 0; i < 20000; i++) {
+            test19_helper1(20);
+            test19_helper2(5);
+        }
+
+        {
+            Method m = test.tests.get("test19");
+            WHITE_BOX.enqueueMethodForCompilation(m, CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION);
+        }
+
+        for (int i = 0; i < 20000; i++) {
+            test20(array);
+        }
+
+        for (int i = 0; i < 20000; i++) {
+            test21(array);
+        }
+
+        if (!success) {
+            throw new RuntimeException("some tests failed");
+        }
+    }
+}