8268347: C2: nested locks optimization may create unbalanced monitor enter/exit code

Reviewed-by: roland, vlivanov, dcubed
This commit is contained in:
Vladimir Kozlov 2021-06-14 23:41:50 +00:00
parent 4a6da99f28
commit 4d8b5c70df
11 changed files with 369 additions and 39 deletions

View file

@ -45,6 +45,9 @@ const char* C2Compiler::retry_no_subsuming_loads() {
const char* C2Compiler::retry_no_escape_analysis() { const char* C2Compiler::retry_no_escape_analysis() {
return "retry without escape analysis"; return "retry without escape analysis";
} }
const char* C2Compiler::retry_no_locks_coarsening() {
return "retry without locks coarsening";
}
const char* C2Compiler::retry_class_loading_during_parsing() { const char* C2Compiler::retry_class_loading_during_parsing() {
return "retry class loading during parsing"; return "retry class loading during parsing";
} }
@ -97,10 +100,11 @@ void C2Compiler::compile_method(ciEnv* env, ciMethod* target, int entry_bci, boo
bool subsume_loads = SubsumeLoads; bool subsume_loads = SubsumeLoads;
bool do_escape_analysis = DoEscapeAnalysis; bool do_escape_analysis = DoEscapeAnalysis;
bool eliminate_boxing = EliminateAutoBox; bool eliminate_boxing = EliminateAutoBox;
bool do_locks_coarsening = EliminateLocks;
while (!env->failing()) { while (!env->failing()) {
// Attempt to compile while subsuming loads into machine instructions. // Attempt to compile while subsuming loads into machine instructions.
Compile C(env, target, entry_bci, subsume_loads, do_escape_analysis, eliminate_boxing, install_code, directive); Compile C(env, target, entry_bci, subsume_loads, do_escape_analysis, eliminate_boxing, do_locks_coarsening, install_code, directive);
// Check result and retry if appropriate. // Check result and retry if appropriate.
if (C.failure_reason() != NULL) { if (C.failure_reason() != NULL) {
@ -120,6 +124,12 @@ void C2Compiler::compile_method(ciEnv* env, ciMethod* target, int entry_bci, boo
env->report_failure(C.failure_reason()); env->report_failure(C.failure_reason());
continue; // retry continue; // retry
} }
if (C.failure_reason_is(retry_no_locks_coarsening())) {
assert(do_locks_coarsening, "must make progress");
do_locks_coarsening = false;
env->report_failure(C.failure_reason());
continue; // retry
}
if (C.has_boxed_value()) { if (C.has_boxed_value()) {
// Recompile without boxing elimination regardless failure reason. // Recompile without boxing elimination regardless failure reason.
assert(eliminate_boxing, "must make progress"); assert(eliminate_boxing, "must make progress");
@ -141,6 +151,10 @@ void C2Compiler::compile_method(ciEnv* env, ciMethod* target, int entry_bci, boo
do_escape_analysis = false; do_escape_analysis = false;
continue; // retry continue; // retry
} }
if (do_locks_coarsening) {
do_locks_coarsening = false;
continue; // retry
}
} }
// print inlining for last compilation only // print inlining for last compilation only
C.dump_print_inlining(); C.dump_print_inlining();

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 1999, 2020, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 1999, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -49,6 +49,7 @@ public:
// sentinel value used to trigger backtracking in compile_method(). // sentinel value used to trigger backtracking in compile_method().
static const char* retry_no_subsuming_loads(); static const char* retry_no_subsuming_loads();
static const char* retry_no_escape_analysis(); static const char* retry_no_escape_analysis();
static const char* retry_no_locks_coarsening();
static const char* retry_class_loading_during_parsing(); static const char* retry_class_loading_during_parsing();
// Print compilation timers and statistics // Print compilation timers and statistics

View file

@ -2054,6 +2054,12 @@ bool AbstractLockNode::find_unlocks_for_region(const RegionNode* region, LockNod
} }
const char* AbstractLockNode::_kind_names[] = {"Regular", "NonEscObj", "Coarsened", "Nested"};
const char * AbstractLockNode::kind_as_string() const {
return _kind_names[_kind];
}
#ifndef PRODUCT #ifndef PRODUCT
// //
// Create a counter which counts the number of times this lock is acquired // Create a counter which counts the number of times this lock is acquired
@ -2071,8 +2077,6 @@ void AbstractLockNode::set_eliminated_lock_counter() {
} }
} }
const char* AbstractLockNode::_kind_names[] = {"Regular", "NonEscObj", "Coarsened", "Nested"};
void AbstractLockNode::dump_spec(outputStream* st) const { void AbstractLockNode::dump_spec(outputStream* st) const {
st->print("%s ", _kind_names[_kind]); st->print("%s ", _kind_names[_kind]);
CallNode::dump_spec(st); CallNode::dump_spec(st);
@ -2124,6 +2128,9 @@ Node *LockNode::Ideal(PhaseGVN *phase, bool can_reshape) {
return result; return result;
} }
if (!phase->C->do_locks_coarsening()) {
return result; // Compiling without locks coarsening
}
// //
// Try lock coarsening // Try lock coarsening
// //
@ -2161,6 +2168,9 @@ Node *LockNode::Ideal(PhaseGVN *phase, bool can_reshape) {
if (PrintEliminateLocks) { if (PrintEliminateLocks) {
int locks = 0; int locks = 0;
int unlocks = 0; int unlocks = 0;
if (Verbose) {
tty->print_cr("=== Locks coarsening ===");
}
for (int i = 0; i < lock_ops.length(); i++) { for (int i = 0; i < lock_ops.length(); i++) {
AbstractLockNode* lock = lock_ops.at(i); AbstractLockNode* lock = lock_ops.at(i);
if (lock->Opcode() == Op_Lock) if (lock->Opcode() == Op_Lock)
@ -2168,10 +2178,11 @@ Node *LockNode::Ideal(PhaseGVN *phase, bool can_reshape) {
else else
unlocks++; unlocks++;
if (Verbose) { if (Verbose) {
lock->dump(1); tty->print(" %d: ", i);
lock->dump();
} }
} }
tty->print_cr("***Eliminated %d unlocks and %d locks", unlocks, locks); tty->print_cr("=== Coarsened %d unlocks and %d locks", unlocks, locks);
} }
#endif #endif
@ -2186,6 +2197,8 @@ Node *LockNode::Ideal(PhaseGVN *phase, bool can_reshape) {
#endif #endif
lock->set_coarsened(); lock->set_coarsened();
} }
// Record this coarsened group.
phase->C->add_coarsened_locks(lock_ops);
} else if (ctrl->is_Region() && } else if (ctrl->is_Region() &&
iter->_worklist.member(ctrl)) { iter->_worklist.member(ctrl)) {
// We weren't able to find any opportunities but the region this // We weren't able to find any opportunities but the region this
@ -2219,15 +2232,34 @@ bool LockNode::is_nested_lock_region(Compile * c) {
// Ignore complex cases: merged locks or multiple locks. // Ignore complex cases: merged locks or multiple locks.
Node* obj = obj_node(); Node* obj = obj_node();
LockNode* unique_lock = NULL; LockNode* unique_lock = NULL;
if (!box->is_simple_lock_region(&unique_lock, obj)) { Node* bad_lock = NULL;
if (!box->is_simple_lock_region(&unique_lock, obj, &bad_lock)) {
#ifdef ASSERT #ifdef ASSERT
this->log_lock_optimization(c, "eliminate_lock_INLR_2a"); this->log_lock_optimization(c, "eliminate_lock_INLR_2a", bad_lock);
#endif #endif
return false; return false;
} }
if (unique_lock != this) { if (unique_lock != this) {
#ifdef ASSERT #ifdef ASSERT
this->log_lock_optimization(c, "eliminate_lock_INLR_2b"); this->log_lock_optimization(c, "eliminate_lock_INLR_2b", (unique_lock != NULL ? unique_lock : bad_lock));
if (PrintEliminateLocks && Verbose) {
tty->print_cr("=============== unique_lock != this ============");
tty->print(" this: ");
this->dump();
tty->print(" box: ");
box->dump();
tty->print(" obj: ");
obj->dump();
if (unique_lock != NULL) {
tty->print(" unique_lock: ");
unique_lock->dump();
}
if (bad_lock != NULL) {
tty->print(" bad_lock: ");
bad_lock->dump();
}
tty->print_cr("===============");
}
#endif #endif
return false; return false;
} }
@ -2294,23 +2326,21 @@ Node *UnlockNode::Ideal(PhaseGVN *phase, bool can_reshape) {
return result; return result;
} }
const char * AbstractLockNode::kind_as_string() const { void AbstractLockNode::log_lock_optimization(Compile *C, const char * tag, Node* bad_lock) const {
return is_coarsened() ? "coarsened" :
is_nested() ? "nested" :
is_non_esc_obj() ? "non_escaping" :
"?";
}
void AbstractLockNode::log_lock_optimization(Compile *C, const char * tag) const {
if (C == NULL) { if (C == NULL) {
return; return;
} }
CompileLog* log = C->log(); CompileLog* log = C->log();
if (log != NULL) { if (log != NULL) {
log->begin_head("%s lock='%d' compile_id='%d' class_id='%s' kind='%s'", Node* box = box_node();
tag, is_Lock(), C->compile_id(), Node* obj = obj_node();
int box_id = box != NULL ? box->_idx : -1;
int obj_id = obj != NULL ? obj->_idx : -1;
log->begin_head("%s compile_id='%d' lock_id='%d' class='%s' kind='%s' box_id='%d' obj_id='%d' bad_id='%d'",
tag, C->compile_id(), this->_idx,
is_Unlock() ? "unlock" : is_Lock() ? "lock" : "?", is_Unlock() ? "unlock" : is_Lock() ? "lock" : "?",
kind_as_string()); kind_as_string(), box_id, obj_id, (bad_lock != NULL ? bad_lock->_idx : -1));
log->stamp(); log->stamp();
log->end_head(); log->end_head();
JVMState* p = is_Unlock() ? (as_Unlock()->dbg_jvms()) : jvms(); JVMState* p = is_Unlock() ? (as_Unlock()->dbg_jvms()) : jvms();

View file

@ -1056,9 +1056,11 @@ private:
Coarsened, // Lock was coarsened Coarsened, // Lock was coarsened
Nested // Nested lock Nested // Nested lock
} _kind; } _kind;
static const char* _kind_names[Nested+1];
#ifndef PRODUCT #ifndef PRODUCT
NamedCounter* _counter; NamedCounter* _counter;
static const char* _kind_names[Nested+1];
#endif #endif
protected: protected:
@ -1101,7 +1103,7 @@ public:
bool is_nested() const { return (_kind == Nested); } bool is_nested() const { return (_kind == Nested); }
const char * kind_as_string() const; const char * kind_as_string() const;
void log_lock_optimization(Compile* c, const char * tag) const; void log_lock_optimization(Compile* c, const char * tag, Node* bad_lock = NULL) const;
void set_non_esc_obj() { _kind = NonEscObj; set_eliminated_lock_counter(); } void set_non_esc_obj() { _kind = NonEscObj; set_eliminated_lock_counter(); }
void set_coarsened() { _kind = Coarsened; set_eliminated_lock_counter(); } void set_coarsened() { _kind = Coarsened; set_eliminated_lock_counter(); }

View file

@ -432,6 +432,7 @@ void Compile::remove_useless_nodes(Unique_Node_List &useful) {
remove_useless_nodes(_skeleton_predicate_opaqs, useful); remove_useless_nodes(_skeleton_predicate_opaqs, useful);
remove_useless_nodes(_expensive_nodes, useful); // remove useless expensive nodes remove_useless_nodes(_expensive_nodes, useful); // remove useless expensive nodes
remove_useless_nodes(_for_post_loop_igvn, useful); // remove useless node recorded for post loop opts IGVN pass remove_useless_nodes(_for_post_loop_igvn, useful); // remove useless node recorded for post loop opts IGVN pass
remove_useless_coarsened_locks(useful); // remove useless coarsened locks nodes
BarrierSetC2* bs = BarrierSet::barrier_set()->barrier_set_c2(); BarrierSetC2* bs = BarrierSet::barrier_set()->barrier_set_c2();
bs->eliminate_useless_gc_barriers(useful, this); bs->eliminate_useless_gc_barriers(useful, this);
@ -501,6 +502,12 @@ void Compile::print_compile_messages() {
tty->print_cr("** Bailout: Recompile without boxing elimination **"); tty->print_cr("** Bailout: Recompile without boxing elimination **");
tty->print_cr("*********************************************************"); tty->print_cr("*********************************************************");
} }
if ((_do_locks_coarsening != EliminateLocks) && PrintOpto) {
// Recompiling without locks coarsening
tty->print_cr("*********************************************************");
tty->print_cr("** Bailout: Recompile without locks coarsening **");
tty->print_cr("*********************************************************");
}
if (env()->break_at_compile()) { if (env()->break_at_compile()) {
// Open the debugger when compiling this method. // Open the debugger when compiling this method.
tty->print("### Breaking when compiling: "); tty->print("### Breaking when compiling: ");
@ -528,13 +535,15 @@ debug_only( int Compile::_debug_idx = 100000; )
Compile::Compile( ciEnv* ci_env, ciMethod* target, int osr_bci, Compile::Compile( ciEnv* ci_env, ciMethod* target, int osr_bci,
bool subsume_loads, bool do_escape_analysis, bool eliminate_boxing, bool install_code, DirectiveSet* directive) bool subsume_loads, bool do_escape_analysis, bool eliminate_boxing,
bool do_locks_coarsening, bool install_code, DirectiveSet* directive)
: Phase(Compiler), : Phase(Compiler),
_compile_id(ci_env->compile_id()), _compile_id(ci_env->compile_id()),
_subsume_loads(subsume_loads), _subsume_loads(subsume_loads),
_do_escape_analysis(do_escape_analysis), _do_escape_analysis(do_escape_analysis),
_install_code(install_code), _install_code(install_code),
_eliminate_boxing(eliminate_boxing), _eliminate_boxing(eliminate_boxing),
_do_locks_coarsening(do_locks_coarsening),
_method(target), _method(target),
_entry_bci(osr_bci), _entry_bci(osr_bci),
_stub_function(NULL), _stub_function(NULL),
@ -566,6 +575,7 @@ Compile::Compile( ciEnv* ci_env, ciMethod* target, int osr_bci,
_skeleton_predicate_opaqs (comp_arena(), 8, 0, NULL), _skeleton_predicate_opaqs (comp_arena(), 8, 0, NULL),
_expensive_nodes (comp_arena(), 8, 0, NULL), _expensive_nodes (comp_arena(), 8, 0, NULL),
_for_post_loop_igvn(comp_arena(), 8, 0, NULL), _for_post_loop_igvn(comp_arena(), 8, 0, NULL),
_coarsened_locks (comp_arena(), 8, 0, NULL),
_congraph(NULL), _congraph(NULL),
NOT_PRODUCT(_printer(NULL) COMMA) NOT_PRODUCT(_printer(NULL) COMMA)
_dead_node_list(comp_arena()), _dead_node_list(comp_arena()),
@ -832,6 +842,7 @@ Compile::Compile( ciEnv* ci_env,
_do_escape_analysis(false), _do_escape_analysis(false),
_install_code(true), _install_code(true),
_eliminate_boxing(false), _eliminate_boxing(false),
_do_locks_coarsening(false),
_method(NULL), _method(NULL),
_entry_bci(InvocationEntryBci), _entry_bci(InvocationEntryBci),
_stub_function(stub_function), _stub_function(stub_function),
@ -4447,6 +4458,101 @@ void Compile::add_expensive_node(Node * n) {
} }
} }
/**
* Track coarsened Lock and Unlock nodes.
*/
class Lock_List : public Node_List {
uint _origin_cnt;
public:
Lock_List(Arena *a, uint cnt) : Node_List(a), _origin_cnt(cnt) {}
uint origin_cnt() const { return _origin_cnt; }
};
void Compile::add_coarsened_locks(GrowableArray<AbstractLockNode*>& locks) {
int length = locks.length();
if (length > 0) {
// Have to keep this list until locks elimination during Macro nodes elimination.
Lock_List* locks_list = new (comp_arena()) Lock_List(comp_arena(), length);
for (int i = 0; i < length; i++) {
AbstractLockNode* lock = locks.at(i);
assert(lock->is_coarsened(), "expecting only coarsened AbstractLock nodes, but got '%s'[%d] node", lock->Name(), lock->_idx);
locks_list->push(lock);
}
_coarsened_locks.append(locks_list);
}
}
void Compile::remove_useless_coarsened_locks(Unique_Node_List& useful) {
int count = coarsened_count();
for (int i = 0; i < count; i++) {
Node_List* locks_list = _coarsened_locks.at(i);
for (uint j = 0; j < locks_list->size(); j++) {
Node* lock = locks_list->at(j);
assert(lock->is_AbstractLock(), "sanity");
if (!useful.member(lock)) {
locks_list->yank(lock);
}
}
}
}
void Compile::remove_coarsened_lock(Node* n) {
if (n->is_AbstractLock()) {
int count = coarsened_count();
for (int i = 0; i < count; i++) {
Node_List* locks_list = _coarsened_locks.at(i);
locks_list->yank(n);
}
}
}
bool Compile::coarsened_locks_consistent() {
int count = coarsened_count();
for (int i = 0; i < count; i++) {
bool unbalanced = false;
bool modified = false; // track locks kind modifications
Lock_List* locks_list = (Lock_List*)_coarsened_locks.at(i);
uint size = locks_list->size();
if (size != locks_list->origin_cnt()) {
unbalanced = true; // Some locks were removed from list
} else {
for (uint j = 0; j < size; j++) {
Node* lock = locks_list->at(j);
// All nodes in group should have the same state (modified or not)
if (!lock->as_AbstractLock()->is_coarsened()) {
if (j == 0) {
// first on list was modified, the rest should be too for consistency
modified = true;
} else if (!modified) {
// this lock was modified but previous locks on the list were not
unbalanced = true;
break;
}
} else if (modified) {
// previous locks on list were modified but not this lock
unbalanced = true;
break;
}
}
}
if (unbalanced) {
// unbalanced monitor enter/exit - only some [un]lock nodes were removed or modified
#ifdef ASSERT
if (PrintEliminateLocks) {
tty->print_cr("=== unbalanced coarsened locks ===");
for (uint l = 0; l < size; l++) {
locks_list->at(l)->dump();
}
}
#endif
record_failure(C2Compiler::retry_no_locks_coarsening());
return false;
}
}
return true;
}
/** /**
* Remove the speculative part of types and clean up the graph * Remove the speculative part of types and clean up the graph
*/ */

View file

@ -46,6 +46,7 @@
#include "runtime/vmThread.hpp" #include "runtime/vmThread.hpp"
#include "utilities/ticks.hpp" #include "utilities/ticks.hpp"
class AbstractLockNode;
class AddPNode; class AddPNode;
class Block; class Block;
class Bundle; class Bundle;
@ -63,6 +64,7 @@ class MachOper;
class MachSafePointNode; class MachSafePointNode;
class Node; class Node;
class Node_Array; class Node_Array;
class Node_List;
class Node_Notes; class Node_Notes;
class NodeCloneInfo; class NodeCloneInfo;
class OptoReg; class OptoReg;
@ -248,6 +250,7 @@ class Compile : public Phase {
const bool _do_escape_analysis; // Do escape analysis. const bool _do_escape_analysis; // Do escape analysis.
const bool _install_code; // Install the code that was compiled const bool _install_code; // Install the code that was compiled
const bool _eliminate_boxing; // Do boxing elimination. const bool _eliminate_boxing; // Do boxing elimination.
const bool _do_locks_coarsening; // Do locks coarsening
ciMethod* _method; // The method being compiled. ciMethod* _method; // The method being compiled.
int _entry_bci; // entry bci for osr methods. int _entry_bci; // entry bci for osr methods.
const TypeFunc* _tf; // My kind of signature const TypeFunc* _tf; // My kind of signature
@ -317,6 +320,7 @@ class Compile : public Phase {
GrowableArray<Node*> _skeleton_predicate_opaqs; // List of Opaque4 nodes for the loop skeleton predicates. GrowableArray<Node*> _skeleton_predicate_opaqs; // List of Opaque4 nodes for the loop skeleton predicates.
GrowableArray<Node*> _expensive_nodes; // List of nodes that are expensive to compute and that we'd better not let the GVN freely common GrowableArray<Node*> _expensive_nodes; // List of nodes that are expensive to compute and that we'd better not let the GVN freely common
GrowableArray<Node*> _for_post_loop_igvn; // List of nodes for IGVN after loop opts are over GrowableArray<Node*> _for_post_loop_igvn; // List of nodes for IGVN after loop opts are over
GrowableArray<Node_List*> _coarsened_locks; // List of coarsened Lock and Unlock nodes
ConnectionGraph* _congraph; ConnectionGraph* _congraph;
#ifndef PRODUCT #ifndef PRODUCT
IdealGraphPrinter* _printer; IdealGraphPrinter* _printer;
@ -508,6 +512,8 @@ class Compile : public Phase {
/** Do aggressive boxing elimination. */ /** Do aggressive boxing elimination. */
bool aggressive_unboxing() const { return _eliminate_boxing && AggressiveUnboxing; } bool aggressive_unboxing() const { return _eliminate_boxing && AggressiveUnboxing; }
bool should_install_code() const { return _install_code; } bool should_install_code() const { return _install_code; }
/** Do locks coarsening. */
bool do_locks_coarsening() const { return _do_locks_coarsening; }
// Other fixed compilation parameters. // Other fixed compilation parameters.
ciMethod* method() const { return _method; } ciMethod* method() const { return _method; }
@ -656,6 +662,7 @@ class Compile : public Phase {
int predicate_count() const { return _predicate_opaqs.length(); } int predicate_count() const { return _predicate_opaqs.length(); }
int skeleton_predicate_count() const { return _skeleton_predicate_opaqs.length(); } int skeleton_predicate_count() const { return _skeleton_predicate_opaqs.length(); }
int expensive_count() const { return _expensive_nodes.length(); } int expensive_count() const { return _expensive_nodes.length(); }
int coarsened_count() const { return _coarsened_locks.length(); }
Node* macro_node(int idx) const { return _macro_nodes.at(idx); } Node* macro_node(int idx) const { return _macro_nodes.at(idx); }
Node* predicate_opaque1_node(int idx) const { return _predicate_opaqs.at(idx); } Node* predicate_opaque1_node(int idx) const { return _predicate_opaqs.at(idx); }
@ -677,6 +684,10 @@ class Compile : public Phase {
if (predicate_count() > 0) { if (predicate_count() > 0) {
_predicate_opaqs.remove_if_existing(n); _predicate_opaqs.remove_if_existing(n);
} }
// Remove from coarsened locks list if present
if (coarsened_count() > 0) {
remove_coarsened_lock(n);
}
} }
void add_expensive_node(Node* n); void add_expensive_node(Node* n);
void remove_expensive_node(Node* n) { void remove_expensive_node(Node* n) {
@ -696,6 +707,10 @@ class Compile : public Phase {
_skeleton_predicate_opaqs.remove_if_existing(n); _skeleton_predicate_opaqs.remove_if_existing(n);
} }
} }
void add_coarsened_locks(GrowableArray<AbstractLockNode*>& locks);
void remove_coarsened_lock(Node* n);
bool coarsened_locks_consistent();
bool post_loop_opts_phase() { return _post_loop_opts_phase; } bool post_loop_opts_phase() { return _post_loop_opts_phase; }
void set_post_loop_opts_phase() { _post_loop_opts_phase = true; } void set_post_loop_opts_phase() { _post_loop_opts_phase = true; }
void reset_post_loop_opts_phase() { _post_loop_opts_phase = false; } void reset_post_loop_opts_phase() { _post_loop_opts_phase = false; }
@ -952,6 +967,8 @@ class Compile : public Phase {
void remove_useless_late_inlines(GrowableArray<CallGenerator*>* inlines, Unique_Node_List &useful); void remove_useless_late_inlines(GrowableArray<CallGenerator*>* inlines, Unique_Node_List &useful);
void remove_useless_late_inlines(GrowableArray<CallGenerator*>* inlines, Node* dead); void remove_useless_late_inlines(GrowableArray<CallGenerator*>* inlines, Node* dead);
void remove_useless_coarsened_locks(Unique_Node_List& useful);
void process_print_inlining(); void process_print_inlining();
void dump_print_inlining(); void dump_print_inlining();
@ -1018,7 +1035,8 @@ class Compile : public Phase {
// continuation. // continuation.
Compile(ciEnv* ci_env, ciMethod* target, Compile(ciEnv* ci_env, ciMethod* target,
int entry_bci, bool subsume_loads, bool do_escape_analysis, int entry_bci, bool subsume_loads, bool do_escape_analysis,
bool eliminate_boxing, bool install_code, DirectiveSet* directive); bool eliminate_boxing, bool do_locks_coarsening,
bool install_code, DirectiveSet* directive);
// Second major entry point. From the TypeFunc signature, generate code // Second major entry point. From the TypeFunc signature, generate code
// to pass arguments from the Java calling convention to the C calling // to pass arguments from the Java calling convention to the C calling

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 1999, 2012, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 1999, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -85,7 +85,7 @@ OptoReg::Name BoxLockNode::reg(Node* box) {
} }
// Is BoxLock node used for one simple lock region (same box and obj)? // Is BoxLock node used for one simple lock region (same box and obj)?
bool BoxLockNode::is_simple_lock_region(LockNode** unique_lock, Node* obj) { bool BoxLockNode::is_simple_lock_region(LockNode** unique_lock, Node* obj, Node** bad_lock) {
LockNode* lock = NULL; LockNode* lock = NULL;
bool has_one_lock = false; bool has_one_lock = false;
for (uint i = 0; i < this->outcnt(); i++) { for (uint i = 0; i < this->outcnt(); i++) {
@ -102,9 +102,15 @@ bool BoxLockNode::is_simple_lock_region(LockNode** unique_lock, Node* obj) {
has_one_lock = true; has_one_lock = true;
} else if (lock != alock->as_Lock()) { } else if (lock != alock->as_Lock()) {
has_one_lock = false; has_one_lock = false;
if (bad_lock != NULL) {
*bad_lock = alock;
}
} }
} }
} else { } else {
if (bad_lock != NULL) {
*bad_lock = alock;
}
return false; // Different objects return false; // Different objects
} }
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 1999, 2019, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 1999, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -63,7 +63,7 @@ public:
void set_eliminated() { _is_eliminated = true; } void set_eliminated() { _is_eliminated = true; }
// Is BoxLock node used for one simple lock region? // Is BoxLock node used for one simple lock region?
bool is_simple_lock_region(LockNode** unique_lock, Node* obj); bool is_simple_lock_region(LockNode** unique_lock, Node* obj, Node** bad_lock);
#ifndef PRODUCT #ifndef PRODUCT
virtual void format( PhaseRegAlloc *, outputStream *st ) const; virtual void format( PhaseRegAlloc *, outputStream *st ) const;

View file

@ -1909,15 +1909,15 @@ void PhaseMacroExpand::expand_allocate_array(AllocateArrayNode *alloc) {
// Mark all associated (same box and obj) lock and unlock nodes for // Mark all associated (same box and obj) lock and unlock nodes for
// elimination if some of them marked already. // elimination if some of them marked already.
void PhaseMacroExpand::mark_eliminated_box(Node* oldbox, Node* obj) { void PhaseMacroExpand::mark_eliminated_box(Node* oldbox, Node* obj) {
if (oldbox->as_BoxLock()->is_eliminated()) if (oldbox->as_BoxLock()->is_eliminated()) {
return; // This BoxLock node was processed already. return; // This BoxLock node was processed already.
}
// New implementation (EliminateNestedLocks) has separate BoxLock // New implementation (EliminateNestedLocks) has separate BoxLock
// node for each locked region so mark all associated locks/unlocks as // node for each locked region so mark all associated locks/unlocks as
// eliminated even if different objects are referenced in one locked region // eliminated even if different objects are referenced in one locked region
// (for example, OSR compilation of nested loop inside locked scope). // (for example, OSR compilation of nested loop inside locked scope).
if (EliminateNestedLocks || if (EliminateNestedLocks ||
oldbox->as_BoxLock()->is_simple_lock_region(NULL, obj)) { oldbox->as_BoxLock()->is_simple_lock_region(NULL, obj, NULL)) {
// Box is used only in one lock region. Mark this box as eliminated. // Box is used only in one lock region. Mark this box as eliminated.
_igvn.hash_delete(oldbox); _igvn.hash_delete(oldbox);
oldbox->as_BoxLock()->set_eliminated(); // This changes box's hash value oldbox->as_BoxLock()->set_eliminated(); // This changes box's hash value
@ -2089,11 +2089,7 @@ bool PhaseMacroExpand::eliminate_locking_node(AbstractLockNode *alock) {
#ifndef PRODUCT #ifndef PRODUCT
if (PrintEliminateLocks) { if (PrintEliminateLocks) {
if (alock->is_Lock()) { tty->print_cr("++++ Eliminated: %d %s '%s'", alock->_idx, (alock->is_Lock() ? "Lock" : "Unlock"), alock->kind_as_string());
tty->print_cr("++++ Eliminated: %d Lock", alock->_idx);
} else {
tty->print_cr("++++ Eliminated: %d Unlock", alock->_idx);
}
} }
#endif #endif
@ -2502,16 +2498,21 @@ void PhaseMacroExpand::eliminate_macro_nodes() {
if (C->macro_count() == 0) if (C->macro_count() == 0)
return; return;
// First, attempt to eliminate locks // Before elimination may re-mark (change to Nested or NonEscObj)
// all associated (same box and obj) lock and unlock nodes.
int cnt = C->macro_count(); int cnt = C->macro_count();
for (int i=0; i < cnt; i++) { for (int i=0; i < cnt; i++) {
Node *n = C->macro_node(i); Node *n = C->macro_node(i);
if (n->is_AbstractLock()) { // Lock and Unlock nodes if (n->is_AbstractLock()) { // Lock and Unlock nodes
// Before elimination mark all associated (same box and obj)
// lock and unlock nodes.
mark_eliminated_locking_nodes(n->as_AbstractLock()); mark_eliminated_locking_nodes(n->as_AbstractLock());
} }
} }
// Re-marking may break consistency of Coarsened locks.
if (!C->coarsened_locks_consistent()) {
return; // recompile without Coarsened locks if broken
}
// First, attempt to eliminate locks
bool progress = true; bool progress = true;
while (progress) { while (progress) {
progress = false; progress = false;
@ -2574,6 +2575,7 @@ void PhaseMacroExpand::eliminate_macro_nodes() {
bool PhaseMacroExpand::expand_macro_nodes() { bool PhaseMacroExpand::expand_macro_nodes() {
// Last attempt to eliminate macro nodes. // Last attempt to eliminate macro nodes.
eliminate_macro_nodes(); eliminate_macro_nodes();
if (C->failing()) return true;
// Eliminate Opaque and LoopLimit nodes. Do it after all loop optimizations. // Eliminate Opaque and LoopLimit nodes. Do it after all loop optimizations.
bool progress = true; bool progress = true;

View file

@ -129,6 +129,7 @@ tier1_compiler_2 = \
tier1_compiler_3 = \ tier1_compiler_3 = \
compiler/intrinsics/ \ compiler/intrinsics/ \
compiler/jsr292/ \ compiler/jsr292/ \
compiler/locks/ \
compiler/loopopts/ \ compiler/loopopts/ \
compiler/macronodes/ \ compiler/macronodes/ \
compiler/memoryinitialization/ \ compiler/memoryinitialization/ \

View file

@ -0,0 +1,150 @@
/*
* Copyright (c) 2021, 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 8268347
* @summary Nested locks optimization may create unbalanced monitor enter/exit code
*
* @run main/othervm -XX:-BackgroundCompilation
* -XX:CompileCommand=dontinline,TestNestedLocksElimination::foo
* -XX:CompileCommand=dontinline,TestNestedLocksElimination::getNext
* -XX:CompileCommand=dontinline,TestNestedLocksElimination::getHolder
* TestNestedLocksElimination
*/
import java.util.LinkedList;
public class TestNestedLocksElimination {
private LinkedList<char[]> buffers = new LinkedList<>();
private boolean complete = false;
private int bufferSize;
void foo(char[] ca) {
// Don't inline dummy method
}
// Don't inline
char[] getNext(int length, int count) {
if (this.buffers.isEmpty()) {
return new char[100];
}
char[] b = (char[]) this.buffers.getFirst();
if (count >= 100) {
this.complete = true;
this.buffers.clear(); // empty
}
return b;
}
synchronized boolean isComplete() {
return this.complete;
}
synchronized boolean availableSegment() {
return (buffers.isEmpty() == false);
}
// Don't inline
TestNestedLocksElimination getHolder(TestNestedLocksElimination s1, TestNestedLocksElimination s2, int count) {
return (count & 7) == 0 ? s2 : s1;
}
int test(TestNestedLocksElimination s1, TestNestedLocksElimination s2, int maxToSend) {
boolean isComplete = true;
boolean availableSegment = false;
int size = 0;
int count = 0;
do {
TestNestedLocksElimination s = getHolder(s1, s2, count++);
synchronized(s) {
isComplete = s.isComplete();
availableSegment = s.availableSegment();
}
synchronized (this) {
size = 0;
while (size < maxToSend) {
char[] b = null;
// This is outer Lock region for object 's'.
// Locks from following inlined methods are "nested"
// because they reference the same object.
synchronized(s) {
b = s.getNext(maxToSend - size, count);
// The next is bi-morphic call with both calls inlined.
// But one is synchronized and the other is not.
// Class check for bi-morphic call is loop invariant
// and will trigger loop unswitching.
// Loop unswitching will create two versions of loop
// with gollowing calls inlinined in both versions.
isComplete = s.isComplete();
// The next synchronized method availableSegment() is
// inlined and its Lock will be "coarsened" with Unlock
// in version of loop with inlined synchronized method
// isComplete().
// Nested Lock Optimization will mark only this Unlock
// as nested (as part of "nested" pair lock/unlock).
// Locks elimination will remove "coarsened" Lock from
// availableSegment() method leaving unmatched unlock.
availableSegment = s.availableSegment();
}
foo(b);
size += b.length;
}
}
} while (availableSegment == true || isComplete == false);
return size;
}
public static void main(String[] args) {
int count = 0;
int n = 0;
TestNestedLocksElimination t = new TestNestedLocksElimination();
TestNestedLocksElimination s1 = new TestNestedLocksElimination();
TestNestedLocksElimination s2 = new TestNestedLocksEliminationSub();
char[] c = new char[100];
while (n++ < 20_000) {
s1.buffers.add(c);
s2.buffers.add(c);
count += t.test(s1, s2, 10000);
}
System.out.println(" count: " + count);
}
}
class TestNestedLocksEliminationSub extends TestNestedLocksElimination {
public boolean isComplete() {
return true;
}
}