Skip to content

Commit

Permalink
8324969: C2: prevent elimination of unbalanced coarsened locking regions
Browse files Browse the repository at this point in the history
Backport-of: b938a5c9edd53821a52b43a8e342b76adb341a3f
  • Loading branch information
TheRealMDoerr committed Jun 13, 2024
1 parent fa896c7 commit ea2aa9e
Show file tree
Hide file tree
Showing 9 changed files with 271 additions and 18 deletions.
5 changes: 3 additions & 2 deletions src/hotspot/share/opto/callnode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1959,7 +1959,7 @@ Node *LockNode::Ideal(PhaseGVN *phase, bool can_reshape) {
// If we are locking an non-escaped object, the lock/unlock is unnecessary
//
ConnectionGraph *cgr = phase->C->congraph();
if (cgr != nullptr && cgr->not_global_escape(obj_node())) {
if (cgr != nullptr && cgr->can_eliminate_lock(this)) {
assert(!is_eliminated() || is_coarsened(), "sanity");
// The lock could be marked eliminated by lock coarsening
// code during first IGVN before EA. Replace coarsened flag
Expand Down Expand Up @@ -2122,6 +2122,7 @@ bool LockNode::is_nested_lock_region(Compile * c) {
obj_node = bs->step_over_gc_barrier(obj_node);
BoxLockNode* box_node = sfn->monitor_box(jvms, idx)->as_BoxLock();
if ((box_node->stack_slot() < stk_slot) && obj_node->eqv_uncast(obj)) {
box->set_nested();
return true;
}
}
Expand Down Expand Up @@ -2155,7 +2156,7 @@ Node *UnlockNode::Ideal(PhaseGVN *phase, bool can_reshape) {
// If we are unlocking an non-escaped object, the lock/unlock is unnecessary.
//
ConnectionGraph *cgr = phase->C->congraph();
if (cgr != nullptr && cgr->not_global_escape(obj_node())) {
if (cgr != nullptr && cgr->can_eliminate_lock(this)) {
assert(!is_eliminated() || is_coarsened(), "sanity");
// The lock could be marked eliminated by lock coarsening
// code during first IGVN before EA. Replace coarsened flag
Expand Down
48 changes: 48 additions & 0 deletions src/hotspot/share/opto/compile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
#include "opto/divnode.hpp"
#include "opto/escape.hpp"
#include "opto/idealGraphPrinter.hpp"
#include "opto/locknode.hpp"
#include "opto/loopnode.hpp"
#include "opto/machnode.hpp"
#include "opto/macro.hpp"
Expand Down Expand Up @@ -4807,10 +4808,25 @@ void Compile::add_coarsened_locks(GrowableArray<AbstractLockNode*>& locks) {
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);
AbstractLockNode* alock = locks.at(0);
BoxLockNode* box = alock->box_node()->as_BoxLock();
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);
BoxLockNode* this_box = lock->box_node()->as_BoxLock();
if (this_box != box) {
// Locking regions (BoxLock) could be Unbalanced here:
// - its coarsened locks were eliminated in earlier
// macro nodes elimination followed by loop unroll
// Preserve Unbalanced status in such cases.
if (!this_box->is_unbalanced()) {
this_box->set_coarsened();
}
if (!box->is_unbalanced()) {
box->set_coarsened();
}
}
}
_coarsened_locks.append(locks_list);
}
Expand Down Expand Up @@ -4888,6 +4904,38 @@ bool Compile::coarsened_locks_consistent() {
return true;
}

// Mark locking regions (identified by BoxLockNode) as unbalanced if
// locks coarsening optimization removed Lock/Unlock nodes from them.
// Such regions become unbalanced because coarsening only removes part
// of Lock/Unlock nodes in region. As result we can't execute other
// locks elimination optimizations which assume all code paths have
// corresponding pair of Lock/Unlock nodes - they are balanced.
void Compile::mark_unbalanced_boxes() const {
int count = coarsened_count();
for (int i = 0; i < count; i++) {
Node_List* locks_list = _coarsened_locks.at(i);
uint size = locks_list->size();
if (size > 0) {
AbstractLockNode* alock = locks_list->at(0)->as_AbstractLock();
BoxLockNode* box = alock->box_node()->as_BoxLock();
if (alock->is_coarsened()) {
// coarsened_locks_consistent(), which is called before this method, verifies
// that the rest of Lock/Unlock nodes on locks_list are also coarsened.
assert(!box->is_eliminated(), "regions with coarsened locks should not be marked as eliminated");
for (uint j = 1; j < size; j++) {
assert(locks_list->at(j)->as_AbstractLock()->is_coarsened(), "only coarsened locks are expected here");
BoxLockNode* this_box = locks_list->at(j)->as_AbstractLock()->box_node()->as_BoxLock();
if (box != this_box) {
assert(!this_box->is_eliminated(), "regions with coarsened locks should not be marked as eliminated");
box->set_unbalanced();
this_box->set_unbalanced();
}
}
}
}
}
}

/**
* Remove the speculative part of types and clean up the graph
*/
Expand Down
1 change: 1 addition & 0 deletions src/hotspot/share/opto/compile.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -742,6 +742,7 @@ class Compile : public Phase {
void add_coarsened_locks(GrowableArray<AbstractLockNode*>& locks);
void remove_coarsened_lock(Node* n);
bool coarsened_locks_consistent();
void mark_unbalanced_boxes() const;

bool post_loop_opts_phase() { return _post_loop_opts_phase; }
void set_post_loop_opts_phase() { _post_loop_opts_phase = true; }
Expand Down
18 changes: 17 additions & 1 deletion src/hotspot/share/opto/escape.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#include "opto/cfgnode.hpp"
#include "opto/compile.hpp"
#include "opto/escape.hpp"
#include "opto/locknode.hpp"
#include "opto/phaseX.hpp"
#include "opto/movenode.hpp"
#include "opto/rootnode.hpp"
Expand Down Expand Up @@ -2095,7 +2096,7 @@ void ConnectionGraph::optimize_ideal_graph(GrowableArray<Node*>& ptr_cmp_worklis
if (n->is_AbstractLock()) { // Lock and Unlock nodes
AbstractLockNode* alock = n->as_AbstractLock();
if (!alock->is_non_esc_obj()) {
if (not_global_escape(alock->obj_node())) {
if (can_eliminate_lock(alock)) {
assert(!alock->is_eliminated() || alock->is_coarsened(), "sanity");
// The lock could be marked eliminated by lock coarsening
// code during first IGVN before EA. Replace coarsened flag
Expand Down Expand Up @@ -2428,6 +2429,21 @@ bool ConnectionGraph::not_global_escape(Node *n) {
return true;
}

// Return true if locked object does not escape globally
// and locked code region (identified by BoxLockNode) is balanced:
// all compiled code paths have corresponding Lock/Unlock pairs.
bool ConnectionGraph::can_eliminate_lock(AbstractLockNode* alock) {
BoxLockNode* box = alock->box_node()->as_BoxLock();
if (!box->is_unbalanced() && not_global_escape(alock->obj_node())) {
if (EliminateNestedLocks) {
// We can mark whole locking region as Local only when only
// one object is used for locking.
box->set_local();
}
return true;
}
return false;
}

// Helper functions

Expand Down
3 changes: 3 additions & 0 deletions src/hotspot/share/opto/escape.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@

class Compile;
class Node;
class AbstractLockNode;
class CallNode;
class PhiNode;
class PhaseTransform;
Expand Down Expand Up @@ -607,6 +608,8 @@ class ConnectionGraph: public ArenaObj {

bool not_global_escape(Node *n);

bool can_eliminate_lock(AbstractLockNode* alock);

// To be used by, e.g., BarrierSetC2 impls
Node* get_addp_base(Node* addp);

Expand Down
46 changes: 39 additions & 7 deletions src/hotspot/share/opto/locknode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,26 +40,55 @@ const RegMask &BoxLockNode::out_RegMask() const {
uint BoxLockNode::size_of() const { return sizeof(*this); }

BoxLockNode::BoxLockNode( int slot ) : Node( Compile::current()->root() ),
_slot(slot), _is_eliminated(false) {
_slot(slot), _kind(BoxLockNode::Regular) {
init_class_id(Class_BoxLock);
init_flags(Flag_rematerialize);
OptoReg::Name reg = OptoReg::stack2reg(_slot);
_inmask.Insert(reg);
}

//-----------------------------hash--------------------------------------------
uint BoxLockNode::hash() const {
if (EliminateNestedLocks)
if (EliminateNestedLocks) {
return NO_HASH; // Each locked region has own BoxLock node
return Node::hash() + _slot + (_is_eliminated ? Compile::current()->fixed_slots() : 0);
}
return Node::hash() + _slot + (is_eliminated() ? Compile::current()->fixed_slots() : 0);
}

//------------------------------cmp--------------------------------------------
bool BoxLockNode::cmp( const Node &n ) const {
if (EliminateNestedLocks)
if (EliminateNestedLocks) {
return (&n == this); // Always fail except on self
}
const BoxLockNode &bn = (const BoxLockNode &)n;
return bn._slot == _slot && bn._is_eliminated == _is_eliminated;
return (bn._slot == _slot) && (bn.is_eliminated() == is_eliminated());
}

Node* BoxLockNode::Identity(PhaseGVN* phase) {
if (!EliminateNestedLocks && !this->is_eliminated()) {
Node* n = phase->hash_find(this);
if (n == nullptr || n == this) {
return this;
}
BoxLockNode* old_box = n->as_BoxLock();
// Set corresponding status (_kind) when commoning BoxLock nodes.
if (this->_kind != old_box->_kind) {
if (this->is_unbalanced()) {
old_box->set_unbalanced();
}
if (!old_box->is_unbalanced()) {
// Only Regular or Coarsened status should be here:
// Nested and Local are set only when EliminateNestedLocks is on.
if (old_box->is_regular()) {
assert(this->is_coarsened(),"unexpected kind: %s", _kind_name[(int)this->_kind]);
old_box->set_coarsened();
} else {
assert(this->is_regular(),"unexpected kind: %s", _kind_name[(int)this->_kind]);
assert(old_box->is_coarsened(),"unexpected kind: %s", _kind_name[(int)old_box->_kind]);
}
}
}
return old_box;
}
return this;
}

BoxLockNode* BoxLockNode::box_node(Node* box) {
Expand All @@ -86,6 +115,9 @@ OptoReg::Name BoxLockNode::reg(Node* box) {

// Is BoxLock node used for one simple lock region (same box and obj)?
bool BoxLockNode::is_simple_lock_region(LockNode** unique_lock, Node* obj, Node** bad_lock) {
if (is_unbalanced()) {
return false;
}
LockNode* lock = nullptr;
bool has_one_lock = false;
for (uint i = 0; i < this->outcnt(); i++) {
Expand Down
68 changes: 63 additions & 5 deletions src/hotspot/share/opto/locknode.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 1999, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1999, 2024, 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
Expand Down Expand Up @@ -33,9 +33,37 @@ class RTMLockingCounters;

//------------------------------BoxLockNode------------------------------------
class BoxLockNode : public Node {
private:
const int _slot; // stack slot
RegMask _inmask; // OptoReg corresponding to stack slot
bool _is_eliminated; // Associated locks were safely eliminated
enum {
Regular = 0, // Normal locking region
Local, // EA found that local not escaping object is used for locking
Nested, // This region is inside other region which use the same object
Coarsened, // Some lock/unlock in region were marked as coarsened
Unbalanced, // This region become unbalanced after coarsened lock/unlock were eliminated
// or it is locking region from OSR when locking is done in Interpreter
Eliminated // All lock/unlock in region were eliminated
} _kind;

#ifdef ASSERT
const char* _kind_name[6] = {
"Regular",
"Local",
"Nested",
"Coarsened",
"Unbalanced",
"Eliminated"
};
#endif

// Allowed transitions of _kind:
// Regular -> Local, Nested, Coarsened
// Local -> Eliminated
// Nested -> Eliminated
// Coarsened -> Local, Nested, Unbalanced
// EA and nested lock elimination can overwrite Coarsened kind.
// Also allow transition to the same kind.

public:
BoxLockNode( int lock );
Expand All @@ -49,6 +77,7 @@ class BoxLockNode : public Node {
virtual bool cmp( const Node &n ) const;
virtual const class Type *bottom_type() const { return TypeRawPtr::BOTTOM; }
virtual uint ideal_reg() const { return Op_RegP; }
virtual Node* Identity(PhaseGVN* phase);

static OptoReg::Name reg(Node* box_node);
static BoxLockNode* box_node(Node* box_node);
Expand All @@ -57,9 +86,38 @@ class BoxLockNode : public Node {
}
int stack_slot() const { return _slot; }

bool is_eliminated() const { return _is_eliminated; }
// mark lock as eliminated.
void set_eliminated() { _is_eliminated = true; }
bool is_regular() const { return _kind == Regular; }
bool is_local() const { return _kind == Local; }
bool is_nested() const { return _kind == Nested; }
bool is_coarsened() const { return _kind == Coarsened; }
bool is_eliminated() const { return _kind == Eliminated; }
bool is_unbalanced() const { return _kind == Unbalanced; }

void set_local() {
assert((_kind == Regular || _kind == Local || _kind == Coarsened),
"incorrect kind for Local transitioni: %s", _kind_name[(int)_kind]);
_kind = Local;
}
void set_nested() {
assert((_kind == Regular || _kind == Nested || _kind == Coarsened),
"incorrect kind for Nested transition: %s", _kind_name[(int)_kind]);
_kind = Nested;
}
void set_coarsened() {
assert((_kind == Regular || _kind == Coarsened),
"incorrect kind for Coarsened transition: %s", _kind_name[(int)_kind]);
_kind = Coarsened;
}
void set_eliminated() {
assert((_kind == Local || _kind == Nested),
"incorrect kind for Eliminated transition: %s", _kind_name[(int)_kind]);
_kind = Eliminated;
}
void set_unbalanced() {
assert((_kind == Coarsened || _kind == Unbalanced),
"incorrect kind for Unbalanced transition: %s", _kind_name[(int)_kind]);
_kind = Unbalanced;
}

// Is BoxLock node used for one simple lock region?
bool is_simple_lock_region(LockNode** unique_lock, Node* obj, Node** bad_lock);
Expand Down
18 changes: 15 additions & 3 deletions src/hotspot/share/opto/macro.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1902,19 +1902,22 @@ void PhaseMacroExpand::expand_allocate_array(AllocateArrayNode *alloc) {
// marked for elimination since new obj has no escape information.
// Mark all associated (same box and obj) lock and unlock nodes for
// elimination if some of them marked already.
void PhaseMacroExpand::mark_eliminated_box(Node* oldbox, Node* obj) {
if (oldbox->as_BoxLock()->is_eliminated()) {
void PhaseMacroExpand::mark_eliminated_box(Node* box, Node* obj) {
BoxLockNode* oldbox = box->as_BoxLock();
if (oldbox->is_eliminated()) {
return; // This BoxLock node was processed already.
}
assert(!oldbox->is_unbalanced(), "this should not be called for unbalanced region");
// New implementation (EliminateNestedLocks) has separate BoxLock
// node for each locked region so mark all associated locks/unlocks as
// eliminated even if different objects are referenced in one locked region
// (for example, OSR compilation of nested loop inside locked scope).
if (EliminateNestedLocks ||
oldbox->as_BoxLock()->is_simple_lock_region(nullptr, obj, nullptr)) {
// Box is used only in one lock region. Mark this box as eliminated.
oldbox->set_local(); // This verifies correct state of BoxLock
_igvn.hash_delete(oldbox);
oldbox->as_BoxLock()->set_eliminated(); // This changes box's hash value
oldbox->set_eliminated(); // This changes box's hash value
_igvn.hash_insert(oldbox);

for (uint i = 0; i < oldbox->outcnt(); i++) {
Expand All @@ -1941,6 +1944,7 @@ void PhaseMacroExpand::mark_eliminated_box(Node* oldbox, Node* obj) {
// Note: BoxLock node is marked eliminated only here and it is used
// to indicate that all associated lock and unlock nodes are marked
// for elimination.
newbox->set_local(); // This verifies correct state of BoxLock
newbox->set_eliminated();
transform_later(newbox);

Expand Down Expand Up @@ -1996,6 +2000,9 @@ void PhaseMacroExpand::mark_eliminated_box(Node* oldbox, Node* obj) {

//-----------------------mark_eliminated_locking_nodes-----------------------
void PhaseMacroExpand::mark_eliminated_locking_nodes(AbstractLockNode *alock) {
if (alock->box_node()->as_BoxLock()->is_unbalanced()) {
return; // Can't do any more elimination for this locking region
}
if (EliminateNestedLocks) {
if (alock->is_nested()) {
assert(alock->box_node()->as_BoxLock()->is_eliminated(), "sanity");
Expand Down Expand Up @@ -2312,6 +2319,11 @@ void PhaseMacroExpand::eliminate_macro_nodes() {
// Re-marking may break consistency of Coarsened locks.
if (!C->coarsened_locks_consistent()) {
return; // recompile without Coarsened locks if broken
} else {
// After coarsened locks are eliminated locking regions
// become unbalanced. We should not execute any more
// locks elimination optimizations on them.
C->mark_unbalanced_boxes();
}

// First, attempt to eliminate locks
Expand Down
Loading

1 comment on commit ea2aa9e

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.