Skip to content

8346989: C2: deoptimization and re-execution cycle with Math.*Exact in case of frequent overflow #23916

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

155 changes: 85 additions & 70 deletions src/hotspot/share/opto/graphKit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -527,71 +527,29 @@ void GraphKit::uncommon_trap_if_should_post_on_exceptions(Deoptimization::DeoptR

//------------------------------builtin_throw----------------------------------
void GraphKit::builtin_throw(Deoptimization::DeoptReason reason) {
bool must_throw = true;

// If this particular condition has not yet happened at this
// bytecode, then use the uncommon trap mechanism, and allow for
// a future recompilation if several traps occur here.
// If the throw is hot, try to use a more complicated inline mechanism
// which keeps execution inside the compiled code.
bool treat_throw_as_hot = false;
ciMethodData* md = method()->method_data();

if (ProfileTraps) {
if (too_many_traps(reason)) {
treat_throw_as_hot = true;
}
// (If there is no MDO at all, assume it is early in
// execution, and that any deopts are part of the
// startup transient, and don't need to be remembered.)

// Also, if there is a local exception handler, treat all throws
// as hot if there has been at least one in this method.
if (C->trap_count(reason) != 0
&& method()->method_data()->trap_count(reason) != 0
&& has_exception_handler()) {
treat_throw_as_hot = true;
}
}
builtin_throw(reason, builtin_throw_exception(reason), /*allow_too_many_traps*/ true);
}

void GraphKit::builtin_throw(Deoptimization::DeoptReason reason,
ciInstance* ex_obj,
bool allow_too_many_traps) {
// If this throw happens frequently, an uncommon trap might cause
// a performance pothole. If there is a local exception handler,
// and if this particular bytecode appears to be deoptimizing often,
// let us handle the throw inline, with a preconstructed instance.
// Note: If the deopt count has blown up, the uncommon trap
// runtime is going to flush this nmethod, not matter what.
if (treat_throw_as_hot && method()->can_omit_stack_trace()) {
// If the throw is local, we use a pre-existing instance and
// punt on the backtrace. This would lead to a missing backtrace
// (a repeat of 4292742) if the backtrace object is ever asked
// for its backtrace.
// Fixing this remaining case of 4292742 requires some flavor of
// escape analysis. Leave that for the future.
ciInstance* ex_obj = nullptr;
switch (reason) {
case Deoptimization::Reason_null_check:
ex_obj = env()->NullPointerException_instance();
break;
case Deoptimization::Reason_div0_check:
ex_obj = env()->ArithmeticException_instance();
break;
case Deoptimization::Reason_range_check:
ex_obj = env()->ArrayIndexOutOfBoundsException_instance();
break;
case Deoptimization::Reason_class_check:
ex_obj = env()->ClassCastException_instance();
break;
case Deoptimization::Reason_array_check:
ex_obj = env()->ArrayStoreException_instance();
break;
default:
break;
}
// If we have a preconstructed exception object, use it.
if (ex_obj != nullptr) {
if (is_builtin_throw_hot(reason)) {
if (method()->can_omit_stack_trace() && ex_obj != nullptr) {
// If the throw is local, we use a pre-existing instance and
// punt on the backtrace. This would lead to a missing backtrace
// (a repeat of 4292742) if the backtrace object is ever asked
// for its backtrace.
// Fixing this remaining case of 4292742 requires some flavor of
// escape analysis. Leave that for the future.
if (env()->jvmti_can_post_on_exceptions()) {
// check if we must post exception events, take uncommon trap if so
uncommon_trap_if_should_post_on_exceptions(reason, must_throw);
uncommon_trap_if_should_post_on_exceptions(reason, true /*must_throw*/);
// here if should_post_on_exceptions is false
// continue on with the normal codegen
}
Expand Down Expand Up @@ -622,6 +580,18 @@ void GraphKit::builtin_throw(Deoptimization::DeoptReason reason) {

add_exception_state(make_exception_state(ex_node));
return;
} else if (builtin_throw_too_many_traps(reason, ex_obj)) {
// We cannot afford to take too many traps here. Suffer in the interpreter instead.
assert(allow_too_many_traps, "not allowed");
if (C->log() != nullptr) {
C->log()->elem("hot_throw preallocated='0' reason='%s' mcount='%d'",
Deoptimization::trap_reason_name(reason),
C->trap_count(reason));
}
uncommon_trap(reason, Deoptimization::Action_none,
(ciKlass*) nullptr, (char*) nullptr,
true /*must_throw*/);
return;
}
}

Expand All @@ -633,27 +603,72 @@ void GraphKit::builtin_throw(Deoptimization::DeoptReason reason) {
// Usual case: Bail to interpreter.
// Reserve the right to recompile if we haven't seen anything yet.

ciMethod* m = Deoptimization::reason_is_speculate(reason) ? C->method() : nullptr;
Deoptimization::DeoptAction action = Deoptimization::Action_maybe_recompile;
if (treat_throw_as_hot
&& (method()->method_data()->trap_recompiled_at(bci(), m)
|| C->too_many_traps(reason))) {
// We cannot afford to take more traps here. Suffer in the interpreter.
if (C->log() != nullptr)
C->log()->elem("hot_throw preallocated='0' reason='%s' mcount='%d'",
Deoptimization::trap_reason_name(reason),
C->trap_count(reason));
action = Deoptimization::Action_none;
}

// "must_throw" prunes the JVM state to include only the stack, if there
// are no local exception handlers. This should cut down on register
// allocation time and code size, by drastically reducing the number
// of in-edges on the call to the uncommon trap.
uncommon_trap(reason, Deoptimization::Action_maybe_recompile,
(ciKlass*) nullptr, (char*) nullptr,
true /*must_throw*/);
}

bool GraphKit::is_builtin_throw_hot(Deoptimization::DeoptReason reason) {
// If this particular condition has not yet happened at this
// bytecode, then use the uncommon trap mechanism, and allow for
// a future recompilation if several traps occur here.
// If the throw is hot, try to use a more complicated inline mechanism
// which keeps execution inside the compiled code.
if (ProfileTraps) {
if (too_many_traps(reason)) {
return true;
}
// (If there is no MDO at all, assume it is early in
// execution, and that any deopts are part of the
// startup transient, and don't need to be remembered.)

// Also, if there is a local exception handler, treat all throws
// as hot if there has been at least one in this method.
if (C->trap_count(reason) != 0 &&
method()->method_data()->trap_count(reason) != 0 &&
has_exception_handler()) {
return true;
}
}
return false;
}

uncommon_trap(reason, action, (ciKlass*)nullptr, (char*)nullptr, must_throw);
bool GraphKit::builtin_throw_too_many_traps(Deoptimization::DeoptReason reason,
ciInstance* ex_obj) {
if (is_builtin_throw_hot(reason)) {
if (method()->can_omit_stack_trace() && ex_obj != nullptr) {
return false; // no traps; throws preallocated exception instead
}
ciMethod* m = Deoptimization::reason_is_speculate(reason) ? C->method() : nullptr;
if (method()->method_data()->trap_recompiled_at(bci(), m) ||
C->too_many_traps(reason)) {
return true;
}
}
return false;
}

ciInstance* GraphKit::builtin_throw_exception(Deoptimization::DeoptReason reason) const {
// Preallocated exception objects to use when we don't need the backtrace.
switch (reason) {
case Deoptimization::Reason_null_check:
return env()->NullPointerException_instance();
case Deoptimization::Reason_div0_check:
return env()->ArithmeticException_instance();
case Deoptimization::Reason_range_check:
return env()->ArrayIndexOutOfBoundsException_instance();
case Deoptimization::Reason_class_check:
return env()->ClassCastException_instance();
case Deoptimization::Reason_array_check:
return env()->ArrayStoreException_instance();
default:
return nullptr;
}
}

//----------------------------PreserveJVMState---------------------------------
PreserveJVMState::PreserveJVMState(GraphKit* kit, bool clone_map) {
Expand Down
10 changes: 10 additions & 0 deletions src/hotspot/share/opto/graphKit.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,16 @@ class GraphKit : public Phase {
// Helper to throw a built-in exception.
// The JVMS must allow the bytecode to be re-executed via an uncommon trap.
void builtin_throw(Deoptimization::DeoptReason reason);
void builtin_throw(Deoptimization::DeoptReason reason,
ciInstance* exception_object,
bool allow_too_many_traps);
bool builtin_throw_too_many_traps(Deoptimization::DeoptReason reason,
ciInstance* exception_object);
private:
bool is_builtin_throw_hot(Deoptimization::DeoptReason reason);
ciInstance* builtin_throw_exception(Deoptimization::DeoptReason reason) const;

public:

// Helper to check the JavaThread::_should_post_on_exceptions flag
// and branch to an uncommon_trap if it is true (with the specified reason and must_throw)
Expand Down
18 changes: 13 additions & 5 deletions src/hotspot/share/opto/library_call.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2003,7 +2003,14 @@ bool LibraryCallKit::inline_min_max(vmIntrinsics::ID id) {
return true;
}

void LibraryCallKit::inline_math_mathExact(Node* math, Node *test) {
bool LibraryCallKit::inline_math_mathExact(Node* math, Node* test) {
if (builtin_throw_too_many_traps(Deoptimization::Reason_intrinsic,
env()->ArithmeticException_instance())) {
// It has been already too many times, but we cannot use builtin_throw (e.g. we care about backtraces),
// so let's bail out intrinsic rather than risking deopting again.
return false;
}

Node* bol = _gvn.transform( new BoolNode(test, BoolTest::overflow) );
IfNode* check = create_and_map_if(control(), bol, PROB_UNLIKELY_MAG(3), COUNT_UNKNOWN);
Node* fast_path = _gvn.transform( new IfFalseNode(check));
Expand All @@ -2017,12 +2024,14 @@ void LibraryCallKit::inline_math_mathExact(Node* math, Node *test) {
set_control(slow_path);
set_i_o(i_o());

uncommon_trap(Deoptimization::Reason_intrinsic,
Deoptimization::Action_none);
builtin_throw(Deoptimization::Reason_intrinsic,
env()->ArithmeticException_instance(),
/*allow_too_many_traps*/ false);
}

set_control(fast_path);
set_result(math);
return true;
}

template <typename OverflowOp>
Expand All @@ -2032,8 +2041,7 @@ bool LibraryCallKit::inline_math_overflow(Node* arg1, Node* arg2) {
MathOp* mathOp = new MathOp(arg1, arg2);
Node* operation = _gvn.transform( mathOp );
Node* ofcheck = _gvn.transform( new OverflowOp(arg1, arg2) );
inline_math_mathExact(operation, ofcheck);
return true;
return inline_math_mathExact(operation, ofcheck);
}

bool LibraryCallKit::inline_math_addExactI(bool is_increment) {
Expand Down
2 changes: 1 addition & 1 deletion src/hotspot/share/opto/library_call.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ class LibraryCallKit : public GraphKit {
bool inline_math_pow();
template <typename OverflowOp>
bool inline_math_overflow(Node* arg1, Node* arg2);
void inline_math_mathExact(Node* math, Node* test);
bool inline_math_mathExact(Node* math, Node* test);
bool inline_math_addExactI(bool is_increment);
bool inline_math_addExactL(bool is_increment);
bool inline_math_multiplyExactI();
Expand Down
Loading