Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/hotspot/share/classfile/vmIntrinsics.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,9 @@ class methodHandle;
do_intrinsic(_Reference_clear0, java_lang_ref_Reference, clear0_name, void_method_signature, F_RN) \
do_intrinsic(_PhantomReference_clear0, java_lang_ref_PhantomReference, clear0_name, void_method_signature, F_RN) \
\
do_intrinsic(_Reference_reachabilityFence, java_lang_ref_Reference, reachabilityFence_name, object_void_signature, F_S) \
do_name(reachabilityFence_name, "reachabilityFence") \
\
/* support for com.sun.crypto.provider.AES_Crypt and some of its callers */ \
do_class(com_sun_crypto_provider_aescrypt, "com/sun/crypto/provider/AES_Crypt") \
do_intrinsic(_aescrypt_encryptBlock, com_sun_crypto_provider_aescrypt, encryptBlock_name, byteArray_int_byteArray_int_signature, F_R) \
Expand Down
4 changes: 3 additions & 1 deletion src/hotspot/share/opto/block.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -179,9 +179,11 @@ int Block::is_Empty() const {

// Ideal nodes (except BoxLock) are allowable in empty blocks: skip them. Only
// Mach and BoxLock nodes turn directly into code via emit().
// Keep ReachabilityFence for diagnostic purposes.
while ((end_idx > 0) &&
!get_node(end_idx)->is_Mach() &&
!get_node(end_idx)->is_BoxLock()) {
!get_node(end_idx)->is_BoxLock() &&
!get_node(end_idx)->is_ReachabilityFence()) {
end_idx--;
}

Expand Down
9 changes: 9 additions & 0 deletions src/hotspot/share/opto/c2_globals.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,15 @@
develop(bool, StressBailout, false, \
"Perform bailouts randomly at C2 failing() checks") \
\
product(bool, OptimizeReachabilityFences, true, DIAGNOSTIC, \
"Optimize reachability fences") \
\
product(bool, PreserveReachabilityFencesOnConstants, false, DIAGNOSTIC, \
"Preserve reachability fences on constants") \
\
product(bool, StressReachabilityFences, false, DIAGNOSTIC, \
"Aggressively insert reachability fences for all oop arguments") \
Comment on lines +79 to +86
Copy link
Contributor

Choose a reason for hiding this comment

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

It could be nice if you gave some more detail here, what these flags do.

\
develop(uint, StressBailoutMean, 100000, \
"The expected number of failing() checks made until " \
"a random bailout.") \
Expand Down
1 change: 1 addition & 0 deletions src/hotspot/share/opto/c2compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -775,6 +775,7 @@ bool C2Compiler::is_intrinsic_supported(vmIntrinsics::ID id) {
case vmIntrinsics::_longBitsToDouble:
case vmIntrinsics::_Reference_get0:
case vmIntrinsics::_Reference_refersTo0:
case vmIntrinsics::_Reference_reachabilityFence:
case vmIntrinsics::_PhantomReference_refersTo0:
case vmIntrinsics::_Reference_clear0:
case vmIntrinsics::_PhantomReference_clear0:
Expand Down
14 changes: 14 additions & 0 deletions src/hotspot/share/opto/callGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,20 @@ void CallGenerator::do_late_inline_helper() {
}

Compile* C = Compile::current();

uint endoff = call->jvms()->endoff();
if (C->inlining_incrementally()) {
// No reachability edges should be present when incremental inlining takes place.
// Inlining logic doesn't expect any extra edges past debug info and fails with
// an assert in SafePointNode::grow_stack.
assert(endoff == call->req(), "reachability edges not supported");
Comment on lines +616 to +620
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we trip over this assert by modifying the reproducer, and add some method somewhere that gets inlined late?

Copy link
Contributor

Choose a reason for hiding this comment

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

Could we also bail out here? Or what would happen now in production if there is a RF edge?

Copy link
Member

Choose a reason for hiding this comment

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

We also use this area past endoff() for storing the "ex_oop" (see for example GraphKit::has_saved_ex_oop()). Are ex_oop and reachability edges mutually exclusive?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, ex_oop and reachability edges are mutually exclusive, but there's no conflict. ex_oop is kept during parsing while reachability edges stay attached to RF nodes until loop optimizations are over (and no inlining can happen anymore).

Copy link
Contributor

@eme64 eme64 Sep 18, 2025

Choose a reason for hiding this comment

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

@dean-long @iwanowww Ok, but probably there will at some point be a conflict. And if RF are rather rare, we will not notice so fast. Or would your stress flag catch the conflict?

Is there not a way to make it clear/explicit which edges are there for what reason?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

would your stress flag catch the conflict?

Yes, w/ -XX:+StressReachabilityFences it becomes very likely that a late inline call has reachability edges.

} else {
if (call->req() > endoff) { // reachability edges present
assert(OptimizeReachabilityFences, "required");
return; // keep the original call node as the holder of reachability info
}
}

// Remove inlined methods from Compiler's lists.
if (call->is_macro()) {
C->remove_macro_node(call);
Expand Down
40 changes: 32 additions & 8 deletions src/hotspot/share/opto/callnode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -893,7 +893,7 @@ bool CallNode::may_modify(const TypeOopPtr* t_oop, PhaseValues* phase) {
}

// Does this call have a direct reference to n other than debug information?
bool CallNode::has_non_debug_use(Node *n) {
bool CallNode::has_non_debug_use(const Node *n) {
const TypeTuple * d = tf()->domain();
for (uint i = TypeFunc::Parms; i < d->cnt(); i++) {
Node *arg = in(i);
Expand Down Expand Up @@ -960,11 +960,12 @@ void CallNode::extract_projections(CallProjections* projs, bool separate_io_proj
for (DUIterator_Fast kmax, k = cn->fast_outs(kmax); k < kmax; k++) {
cpn = cn->fast_out(k)->as_Proj();
assert(cpn->is_CatchProj(), "must be a CatchProjNode");
if (cpn->_con == CatchProjNode::fall_through_index)
projs->fallthrough_catchproj = cpn;
else {
assert(cpn->_con == CatchProjNode::catch_all_index, "must be correct index.");
projs->catchall_catchproj = cpn;
switch (cpn->_con) {
case CatchProjNode::fall_through_index: projs->fallthrough_catchproj = cpn; break;
case CatchProjNode::catch_all_index: projs->catchall_catchproj = cpn; break;
default: {
assert(cpn->_con > 1, "not an exception table projection"); // exception table; rethrow case
}
}
}
}
Expand All @@ -978,8 +979,12 @@ void CallNode::extract_projections(CallProjections* projs, bool separate_io_proj
for (DUIterator j = pn->outs(); pn->has_out(j); j++) {
Node* e = pn->out(j);
if (e->Opcode() == Op_CreateEx && e->in(0)->is_CatchProj() && e->outcnt() > 0) {
assert(projs->exobj == nullptr, "only one");
projs->exobj = e;
if (e->in(0)->as_CatchProj()->_con == CatchProjNode::catch_all_index) {
assert(projs->exobj == nullptr, "only one");
projs->exobj = e;
} else {
assert(e->in(0)->as_CatchProj()->_con > 1, "not an exception table projection"); // exception table for rethrow case
}
}
}
break;
Expand Down Expand Up @@ -1577,6 +1582,25 @@ void SafePointNode::disconnect_from_root(PhaseIterGVN *igvn) {
}
}

void SafePointNode::remove_non_debug_edges(GrowableArray<Node*>& non_debug_edges) {
assert(non_debug_edges.is_empty(), "edges not processed");
while (req() > jvms()->endoff()) {
uint last = req() - 1;
non_debug_edges.push(in(last));
del_req(last);
}
assert(jvms()->endoff() == req(), "no extra edges past debug info allowed");
}

void SafePointNode::restore_non_debug_edges(GrowableArray<Node*>& non_debug_edges) {
assert(jvms()->endoff() == req(), "no extra edges past debug info allowed");
while (non_debug_edges.is_nonempty()) {
Node* non_debug_edge = non_debug_edges.pop();
add_req(non_debug_edge);
}
assert(non_debug_edges.is_empty(), "edges not processed");
}

//============== SafePointScalarObjectNode ==============

SafePointScalarObjectNode::SafePointScalarObjectNode(const TypeOopPtr* tp, Node* alloc, uint first_index, uint depth, uint n_fields) :
Expand Down
5 changes: 4 additions & 1 deletion src/hotspot/share/opto/callnode.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,9 @@ class SafePointNode : public MultiNode {
return _has_ea_local_in_scope;
}

void remove_non_debug_edges(GrowableArray<Node*>& non_debug_edges);
void restore_non_debug_edges(GrowableArray<Node*>& non_debug_edges);

void disconnect_from_root(PhaseIterGVN *igvn);

// Standard Node stuff
Expand Down Expand Up @@ -737,7 +740,7 @@ class CallNode : public SafePointNode {
// Returns true if the call may modify n
virtual bool may_modify(const TypeOopPtr* t_oop, PhaseValues* phase);
// Does this node have a use of n other than in debug information?
bool has_non_debug_use(Node* n);
bool has_non_debug_use(const Node* n);
// Returns the unique CheckCastPP of a call
// or result projection is there are several CheckCastPP
// or returns null if there is no one.
Expand Down
1 change: 1 addition & 0 deletions src/hotspot/share/opto/classes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
#include "opto/narrowptrnode.hpp"
#include "opto/node.hpp"
#include "opto/opaquenode.hpp"
#include "opto/reachability.hpp"
#include "opto/rootnode.hpp"
#include "opto/subnode.hpp"
#include "opto/subtypenode.hpp"
Expand Down
1 change: 1 addition & 0 deletions src/hotspot/share/opto/classes.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -541,3 +541,4 @@ macro(MaskAll)
macro(AndVMask)
macro(OrVMask)
macro(XorVMask)
macro(ReachabilityFence)
55 changes: 38 additions & 17 deletions src/hotspot/share/opto/compile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
#include "opto/output.hpp"
#include "opto/parse.hpp"
#include "opto/phaseX.hpp"
#include "opto/reachability.hpp"
#include "opto/rootnode.hpp"
#include "opto/runtime.hpp"
#include "opto/stringopts.hpp"
Expand Down Expand Up @@ -396,6 +397,9 @@ void Compile::remove_useless_node(Node* dead) {
if (dead->is_expensive()) {
remove_expensive_node(dead);
}
if (dead->is_ReachabilityFence()) {
remove_reachability_fence(dead->as_ReachabilityFence());
}
if (dead->is_OpaqueTemplateAssertionPredicate()) {
remove_template_assertion_predicate_opaque(dead->as_OpaqueTemplateAssertionPredicate());
}
Expand Down Expand Up @@ -459,6 +463,7 @@ void Compile::disconnect_useless_nodes(Unique_Node_List& useful, Unique_Node_Lis
// Remove useless Template Assertion Predicate opaque nodes
remove_useless_nodes(_template_assertion_predicate_opaques, useful);
remove_useless_nodes(_expensive_nodes, useful); // remove useless expensive nodes
remove_useless_nodes(_reachability_fences, 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_nodes(_for_merge_stores_igvn, useful); // remove useless node recorded for merge stores IGVN pass
remove_useless_unstable_if_traps(useful); // remove useless unstable_if traps
Expand Down Expand Up @@ -663,6 +668,7 @@ Compile::Compile(ciEnv* ci_env, ciMethod* target, int osr_bci,
_parse_predicates(comp_arena(), 8, 0, nullptr),
_template_assertion_predicate_opaques(comp_arena(), 8, 0, nullptr),
_expensive_nodes(comp_arena(), 8, 0, nullptr),
_reachability_fences(comp_arena(), 8, 0, nullptr),
_for_post_loop_igvn(comp_arena(), 8, 0, nullptr),
_for_merge_stores_igvn(comp_arena(), 8, 0, nullptr),
_unstable_if_traps(comp_arena(), 8, 0, nullptr),
Expand Down Expand Up @@ -932,6 +938,7 @@ Compile::Compile(ciEnv* ci_env,
_directive(directive),
_log(ci_env->log()),
_first_failure_details(nullptr),
_reachability_fences(comp_arena(), 8, 0, nullptr),
_for_post_loop_igvn(comp_arena(), 8, 0, nullptr),
_for_merge_stores_igvn(comp_arena(), 8, 0, nullptr),
_congraph(nullptr),
Expand Down Expand Up @@ -2512,12 +2519,21 @@ void Compile::Optimize() {
return;
}

if (failing()) return;

C->clear_major_progress(); // ensure that major progress is now clear

process_for_post_loop_opts_igvn(igvn);

// Once loop optimizations are over, it is safe to get rid of all reachability fence nodes and
// migrate reachability edges to safepoints.
if (OptimizeReachabilityFences && _reachability_fences.length() > 0) {
TracePhase tp1(_t_idealLoop);
TracePhase tp2(_t_reachability);
PhaseIdealLoop::optimize(igvn, PostLoopOptsEliminateReachabilityFences);
print_method(PHASE_ELIMINATE_REACHABILITY_FENCES, 2);
if (failing()) return;
assert(_reachability_fences.length() == 0 || PreserveReachabilityFencesOnConstants, "no RF nodes allowed");
}

process_for_merge_stores_igvn(igvn);

if (failing()) return;
Expand Down Expand Up @@ -3973,11 +3989,29 @@ void Compile::final_graph_reshaping_walk(Node_Stack& nstack, Node* root, Final_R
}
}

expand_reachability_fences(sfpt);

// Skip next transformation if compressed oops are not used.
if ((UseCompressedOops && !Matcher::gen_narrow_oop_implicit_null_checks()) ||
(!UseCompressedOops && !UseCompressedClassPointers))
return;

// Go over ReachabilityFence nodes to skip DecodeN nodes for referents.
// The sole purpose of RF node is to keep the referent oop alive and
// decoding the oop for that is not needed.
for (int i = 0; i < C->reachability_fences_count(); i++) {
ReachabilityFenceNode* rf = C->reachability_fence(i);
DecodeNNode* dn = rf->in(1)->isa_DecodeN();
if (dn != nullptr) {
if (!dn->has_non_debug_uses() || Matcher::narrow_oop_use_complex_address()) {
rf->set_req(1, dn->in(1));
if (dn->outcnt() == 0) {
dn->disconnect_inputs(this);
}
}
}
}

// Go over safepoints nodes to skip DecodeN/DecodeNKlass nodes for debug edges.
// It could be done for an uncommon traps or any safepoints/calls
// if the DecodeN/DecodeNKlass node is referenced only in a debug info.
Expand All @@ -3991,21 +4025,8 @@ void Compile::final_graph_reshaping_walk(Node_Stack& nstack, Node* root, Final_R
n->as_CallStaticJava()->uncommon_trap_request() != 0);
for (int j = start; j < end; j++) {
Node* in = n->in(j);
if (in->is_DecodeNarrowPtr()) {
bool safe_to_skip = true;
if (!is_uncommon ) {
// Is it safe to skip?
for (uint i = 0; i < in->outcnt(); i++) {
Node* u = in->raw_out(i);
if (!u->is_SafePoint() ||
(u->is_Call() && u->as_Call()->has_non_debug_use(n))) {
safe_to_skip = false;
}
}
}
if (safe_to_skip) {
n->set_req(j, in->in(1));
}
if (in->is_DecodeNarrowPtr() && (is_uncommon || !in->has_non_debug_uses())) {
n->set_req(j, in->in(1));
Comment on lines -3994 to +4029
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you say why you changed this code here? Is it equivalent?

if (in->outcnt() == 0) {
in->disconnect_inputs(this);
}
Expand Down
19 changes: 17 additions & 2 deletions src/hotspot/share/opto/compile.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ class PhaseIterGVN;
class PhaseRegAlloc;
class PhaseCCP;
class PhaseOutput;
class ReachabilityFenceNode;
class RootNode;
class relocInfo;
class StartNode;
Expand Down Expand Up @@ -107,7 +108,8 @@ enum LoopOptsMode {
LoopOptsMaxUnroll,
LoopOptsShenandoahExpand,
LoopOptsSkipSplitIf,
LoopOptsVerify
LoopOptsVerify,
PostLoopOptsEliminateReachabilityFences
};

// The type of all node counts and indexes.
Expand Down Expand Up @@ -375,6 +377,7 @@ class Compile : public Phase {
// of Template Assertion Predicates themselves.
GrowableArray<OpaqueTemplateAssertionPredicateNode*> _template_assertion_predicate_opaques;
GrowableArray<Node*> _expensive_nodes; // List of nodes that are expensive to compute and that we'd better not let the GVN freely common
GrowableArray<ReachabilityFenceNode*> _reachability_fences; // List of reachability fences
GrowableArray<Node*> _for_post_loop_igvn; // List of nodes for IGVN after loop opts are over
GrowableArray<Node*> _for_merge_stores_igvn; // List of nodes for IGVN merge stores
GrowableArray<UnstableIfTrap*> _unstable_if_traps; // List of ifnodes after IGVN
Expand Down Expand Up @@ -702,11 +705,13 @@ class Compile : public Phase {
int template_assertion_predicate_count() const { return _template_assertion_predicate_opaques.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* expensive_node(int idx) const { return _expensive_nodes.at(idx); }

ReachabilityFenceNode* reachability_fence(int idx) const { return _reachability_fences.at(idx); }
int reachability_fences_count() const { return _reachability_fences.length(); }

ConnectionGraph* congraph() { return _congraph;}
void set_congraph(ConnectionGraph* congraph) { _congraph = congraph;}
void add_macro_node(Node * n) {
Expand All @@ -728,6 +733,14 @@ class Compile : public Phase {
_expensive_nodes.remove_if_existing(n);
}

void add_reachability_fence(ReachabilityFenceNode* rf) {
_reachability_fences.append(rf);
}

void remove_reachability_fence(ReachabilityFenceNode* n) {
_reachability_fences.remove_if_existing(n);
}

void add_parse_predicate(ParsePredicateNode* n) {
assert(!_parse_predicates.contains(n), "duplicate entry in Parse Predicate list");
_parse_predicates.append(n);
Expand Down Expand Up @@ -1280,6 +1293,8 @@ class Compile : public Phase {
// Definitions of pd methods
static void pd_compiler2_init();

void expand_reachability_fences(Unique_Node_List& safepoints);

// Static parse-time type checking logic for gen_subtype_check:
enum SubTypeCheckResult { SSC_always_false, SSC_always_true, SSC_easy_test, SSC_full_test };
SubTypeCheckResult static_subtype_check(const TypeKlassPtr* superk, const TypeKlassPtr* subk, bool skip = StressReflectiveCode);
Expand Down
Loading