--- old/src/share/vm/oops/methodData.cpp 2015-03-17 10:09:04.768078581 +0100 +++ new/src/share/vm/oops/methodData.cpp 2015-03-17 10:09:04.468791120 +0100 @@ -152,6 +152,7 @@ void BitData::print_data_on(outputStream* st, const char* extra) const { print_shared(st, "BitData", extra); + st->cr(); } // ================================================================== --- old/src/share/vm/runtime/deoptimization.cpp 2015-03-17 10:09:04.769290407 +0100 +++ new/src/share/vm/runtime/deoptimization.cpp 2015-03-17 10:09:04.384557040 +0100 @@ -1861,6 +1861,7 @@ "speculate_null_check", "rtm_state_change", "unstable_if", + "unstable_fused_if", "tenured" }; const char* Deoptimization::_trap_action_name[] = { --- old/src/share/vm/opto/node.cpp 2015-03-17 10:09:04.795306354 +0100 +++ new/src/share/vm/opto/node.cpp 2015-03-17 10:09:04.436067405 +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); --- old/src/share/vm/oops/methodData.hpp 2015-03-17 10:09:04.796088415 +0100 +++ new/src/share/vm/oops/methodData.hpp 2015-03-17 10:09:04.525928024 +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 --- old/src/share/vm/opto/ifnode.cpp 2015-03-17 10:09:04.822652028 +0100 +++ new/src/share/vm/opto/ifnode.cpp 2015-03-17 10:09:04.592479175 +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); + + 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; } - - 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; - } + 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. --- old/src/share/vm/opto/loopopts.cpp 2015-03-17 10:09:04.838560969 +0100 +++ new/src/share/vm/opto/loopopts.cpp 2015-03-17 10:09:04.498989639 +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. --- old/src/share/vm/opto/node.hpp 2015-03-17 10:09:04.865531305 +0100 +++ new/src/share/vm/opto/node.hpp 2015-03-17 10:09:04.552955799 +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 --- old/src/share/vm/opto/multnode.hpp 2015-03-17 10:09:04.861307654 +0100 +++ new/src/share/vm/opto/multnode.hpp 2015-03-17 10:09:04.407865136 +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 --- old/src/share/vm/opto/cfgnode.hpp 2015-03-17 10:09:04.952870068 +0100 +++ new/src/share/vm/opto/cfgnode.hpp 2015-03-17 10:09:04.617781575 +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 --- old/src/share/vm/opto/multnode.cpp 2015-03-17 10:09:04.982891021 +0100 +++ new/src/share/vm/opto/multnode.cpp 2015-03-17 10:09:04.685887304 +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); } --- old/src/share/vm/opto/subnode.hpp 2015-03-17 10:09:05.033838201 +0100 +++ new/src/share/vm/opto/subnode.hpp 2015-03-17 10:09:04.708455032 +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; }; --- old/src/share/vm/opto/macro.cpp 2015-03-17 10:09:05.011060230 +0100 +++ new/src/share/vm/opto/macro.cpp 2015-03-17 10:09:04.653997758 +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) { --- old/src/share/vm/runtime/deoptimization.hpp 2015-03-17 10:09:08.080666037 +0100 +++ new/src/share/vm/runtime/deoptimization.hpp 2015-03-17 10:09:07.908181606 +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; } --- old/src/share/vm/utilities/globalDefinitions.hpp 2015-03-17 10:09:08.150624348 +0100 +++ new/src/share/vm/utilities/globalDefinitions.hpp 2015-03-17 10:09:07.979565078 +0100 @@ -1354,6 +1354,13 @@ return (intptr_t) p; } +// swap a & b +template 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 --- old/src/share/vm/runtime/vmStructs.cpp 2015-03-17 10:09:08.158594775 +0100 +++ new/src/share/vm/runtime/vmStructs.cpp 2015-03-17 10:09:07.939838113 +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) \ --- /dev/null 2015-02-18 19:04:22.401028410 +0100 +++ new/test/compiler/rangechecks/TestExplicitRangeChecks.java 2015-03-17 10:09:08.192185407 +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 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"); + } + } +}