8388406: [BACKOUT] C2: crash in compiled code due to zero division because of widened CastII

Reviewed-by: thartmann, chagedorn
This commit is contained in:
Roland Westrelin 2026-07-17 15:02:34 +00:00
parent 9e9fae6584
commit 5b3456ce70
23 changed files with 81 additions and 2793 deletions

View File

@ -922,8 +922,8 @@
"Use StoreStore barrier instead of Release barrier at the end " \
"of constructors") \
\
develop(bool, KillPathsReachableByDeadDataNode, true, \
"When a data node becomes top, make paths where the node is " \
develop(bool, KillPathsReachableByDeadTypeNode, true, \
"When a Type node becomes top, make paths where the node is " \
"used dead by replacing them with a Halt node. Turning this off " \
"could corrupt the graph in rare cases and should be used with " \
"care.") \

View File

@ -111,6 +111,9 @@ Node* ConstraintCastNode::Ideal(PhaseGVN* phase, bool can_reshape) {
if (in(0) != nullptr && remove_dead_region(phase, can_reshape)) {
return this;
}
if (in(1) != nullptr && phase->type(in(1)) != Type::TOP) {
return TypeNode::Ideal(phase, can_reshape);
}
return nullptr;
}

View File

@ -693,13 +693,14 @@ Node *RegionNode::Ideal(PhaseGVN *phase, bool can_reshape) {
if (add_to_worklist) {
igvn->add_users_to_worklist(this); // Check for further allowed opts
}
uint edges_removed;
for (DUIterator_Last imin, i = last_outs(imin); i >= imin; i -= edges_removed) {
edges_removed = 1;
for (DUIterator_Last imin, i = last_outs(imin); i >= imin; --i) {
Node* n = last_out(i);
igvn->hash_delete(n); // Remove from worklist before modifying edges
if (n->outcnt() == 0) {
edges_removed = n->replace_edge(this, phase->C->top(), igvn);
int uses_found = n->replace_edge(this, phase->C->top(), igvn);
if (uses_found > 1) { // (--i) done at the end of the loop.
i -= (uses_found - 1);
}
continue;
}
if( n->is_Phi() ) { // Collapse all Phis
@ -718,7 +719,10 @@ Node *RegionNode::Ideal(PhaseGVN *phase, bool can_reshape) {
}
else if( n->is_Region() ) { // Update all incoming edges
assert(n != this, "Must be removed from DefUse edges");
edges_removed = n->replace_edge(this, parent_ctrl, igvn);
int uses_found = n->replace_edge(this, parent_ctrl, igvn);
if (uses_found > 1) { // (--i) done at the end of the loop.
i -= (uses_found - 1);
}
}
else {
assert(n->in(0) == this, "Expect RegionNode to be control parent");

View File

@ -121,7 +121,6 @@ macro(CompareAndExchangeI)
macro(CompareAndExchangeL)
macro(CompareAndExchangeP)
macro(CompareAndExchangeN)
macro(DeadPath)
macro(GetAndAddB)
macro(GetAndAddS)
macro(GetAndAddI)

View File

@ -312,8 +312,6 @@ void Compile::identify_useful_nodes(Unique_Node_List &useful) {
// If 'top' is cached, declare it useful to preserve cached node
if (cached_top_node()) { useful.push(cached_top_node()); }
if (dead_path()) { useful.push(dead_path()); }
// Push all useful nodes onto the list, breadthfirst
for( uint next = 0; next < useful.size(); ++next ) {
assert( next < unique(), "Unique useful nodes < total nodes");
@ -390,7 +388,7 @@ void Compile::remove_useless_node(Node* dead) {
// it reachable by adding use edges. So, we will NOT count Con nodes
// as dead to be conservative about the dead node count at any
// given time.
if (!dead->is_Con() && dead != dead_path()) {
if (!dead->is_Con()) {
record_dead_node(dead->_idx);
}
if (dead->is_macro()) {
@ -686,7 +684,6 @@ Compile::Compile(ciEnv* ci_env, ciMethod* target, int osr_bci,
_node_arena_one(mtCompiler, Arena::Tag::tag_node),
_node_arena_two(mtCompiler, Arena::Tag::tag_node),
_node_arena(&_node_arena_one),
_dead_path(nullptr),
_mach_constant_base_node(nullptr),
_Compile_types(mtCompiler, Arena::Tag::tag_type),
_initial_gvn(nullptr),
@ -757,7 +754,6 @@ Compile::Compile(ciEnv* ci_env, ciMethod* target, int osr_bci,
}
Init(/*do_aliasing=*/ true);
set_dead_path(new DeadPathNode());
print_compile_messages();
@ -967,7 +963,6 @@ Compile::Compile(ciEnv* ci_env,
_node_arena_one(mtCompiler, Arena::Tag::tag_node),
_node_arena_two(mtCompiler, Arena::Tag::tag_node),
_node_arena(&_node_arena_one),
_dead_path(nullptr),
_mach_constant_base_node(nullptr),
_Compile_types(mtCompiler, Arena::Tag::tag_type),
_initial_gvn(nullptr),
@ -2635,9 +2630,6 @@ void Compile::Optimize() {
}
}
// Unique DeadPath node should not be used anymore
_dead_path = nullptr;
print_method(PHASE_OPTIMIZE_FINISHED, 2);
DEBUG_ONLY(set_phase_optimize_finished();)
}
@ -3946,21 +3938,6 @@ void Compile::final_graph_reshaping_main_switch(Node* n, Final_Reshape_Counts& f
break;
}
#endif
case Op_DeadPath: {
// The CFG inputs are dead paths. Replace the DeadPath with a Region and insert a Halt node.
assert(n->req() > 1, "why not removed if no input other than itself?");
RegionNode* r = new RegionNode(n->req());
for (uint i = 1; i < n->req(); ++i) {
r->set_req(i, n->in(i));
}
n->disconnect_inputs(this);
Node* frame = start()->proj_out(TypeFunc::FramePtr);
stringStream ss;
ss.print("dead path discovered by data nodes during igvn");
Node* halt = new HaltNode(r, frame, ss.as_string(comp_arena()));
root()->set_req(root()->find_edge(n), halt);
break;
}
default:
assert(!n->is_Call(), "");
assert(!n->is_Mem(), "");

View File

@ -57,7 +57,6 @@ class CallStaticJavaNode;
class CloneMap;
class CompilationFailureInfo;
class ConnectionGraph;
class DeadPathNode;
class IdealGraphPrinter;
class InlineTree;
class Matcher;
@ -428,7 +427,7 @@ public:
private:
RootNode* _root; // Unique root of compilation, or null after bail-out.
Node* _top; // Unique top node. (Reset by various phases.)
DeadPathNode* _dead_path; // Unique DeadPath node
Node* _immutable_memory; // Initial memory state
Node* _recent_alloc_obj;
@ -898,12 +897,6 @@ public:
Arena* old_arena() { return (&_node_arena_one == _node_arena) ? &_node_arena_two : &_node_arena_one; }
RootNode* root() const { return _root; }
void set_root(RootNode* r) { _root = r; }
DeadPathNode* dead_path() const { return _dead_path; }
void set_dead_path(DeadPathNode* dead_path) {
assert(_dead_path == nullptr, "can only set once");
_dead_path = dead_path;
}
StartNode* start() const; // (Derived from root.)
void verify_start(StartNode* s) const NOT_DEBUG_RETURN;
Node* immutable_memory();

View File

@ -755,6 +755,13 @@ bool Compile::push_thru_add(PhaseGVN* phase, Node* z, const TypeInteger* tz, con
//------------------------------Ideal------------------------------------------
Node* ConvI2LNode::Ideal(PhaseGVN* phase, bool can_reshape) {
if (in(1) != nullptr && phase->type(in(1)) != Type::TOP) {
Node* progress = TypeNode::Ideal(phase, can_reshape);
if (progress != nullptr) {
return progress;
}
}
const TypeLong* this_type = this->type()->is_long();
if (can_reshape && !phase->C->post_loop_opts_phase()) {
// makes sure we run ::Value to potentially remove type assertion after loop opts
@ -857,6 +864,13 @@ const Type* ConvL2INode::Value(PhaseGVN* phase) const {
// Return a node which is more "ideal" than the current node.
// Blow off prior masking to int
Node* ConvL2INode::Ideal(PhaseGVN* phase, bool can_reshape) {
if (in(1) != nullptr && phase->type(in(1)) != Type::TOP) {
Node* progress = TypeNode::Ideal(phase, can_reshape);
if (progress != nullptr) {
return progress;
}
}
Node *andl = in(1);
uint andl_op = andl->Opcode();
if( andl_op == Op_AndL ) {

View File

@ -1031,10 +1031,6 @@ const Type* UDivINode::Value(PhaseGVN* phase) const {
if( t1 == Type::TOP ) return Type::TOP;
if( t2 == Type::TOP ) return Type::TOP;
if (t2 == TypeInt::ZERO) {
return Type::TOP;
}
// x/x == 1 since we always generate the dynamic divisor check for 0.
if (in(1) == in(2)) {
return TypeInt::ONE;
@ -1071,10 +1067,6 @@ const Type* UDivLNode::Value(PhaseGVN* phase) const {
if( t1 == Type::TOP ) return Type::TOP;
if( t2 == Type::TOP ) return Type::TOP;
if (t2 == TypeLong::ZERO) {
return Type::TOP;
}
// x/x == 1 since we always generate the dynamic divisor check for 0.
if (in(1) == in(2)) {
return TypeLong::ONE;
@ -1388,9 +1380,6 @@ Node* UModINode::Ideal(PhaseGVN* phase, bool can_reshape) {
}
const Type* UModINode::Value(PhaseGVN* phase) const {
if (phase->type(in(2)) == TypeInt::ZERO) {
return Type::TOP;
}
return unsigned_mod_value<TypeInt, juint, jint>(phase, this);
}
@ -1531,9 +1520,6 @@ Node *UModLNode::Ideal(PhaseGVN *phase, bool can_reshape) {
}
const Type* UModLNode::Value(PhaseGVN* phase) const {
if (phase->type(in(2)) == TypeLong::ZERO) {
return Type::TOP;
}
return unsigned_mod_value<TypeLong, julong, jlong>(phase, this);
}

View File

@ -40,9 +40,7 @@ private:
bool _pinned;
protected:
DivModIntegerNode(Node* c, Node* dividend, Node* divisor) : Node(c, dividend, divisor), _pinned(false) {
init_class_id(Class_DivModInteger);
}
DivModIntegerNode(Node* c, Node* dividend, Node* divisor) : Node(c, dividend, divisor), _pinned(false) {}
private:
virtual uint size_of() const override { return sizeof(DivModIntegerNode); }
@ -54,15 +52,6 @@ private:
res->_pinned = true;
return res;
}
public:
const TypeInteger* zero() const {
if (bottom_type() == TypeInt::INT) {
return TypeInt::ZERO;
}
assert(bottom_type() == TypeLong::LONG, "should be int or long");
return TypeLong::ZERO;
}
};
//------------------------------DivINode---------------------------------------

View File

@ -1725,7 +1725,7 @@ void PhaseIdealLoop::try_sink_out_of_loop(Node* n) {
!n->is_OpaqueTemplateAssertionPredicate() &&
!is_raw_to_oop_cast && // don't extend live ranges of raw oops
n->Opcode() != Op_CreateEx &&
(KillPathsReachableByDeadDataNode || !n->is_Type())
(KillPathsReachableByDeadTypeNode || !n->is_Type())
) {
Node *n_ctrl = get_ctrl(n);
IdealLoopTree *n_loop = get_loop(n_ctrl);

View File

@ -90,6 +90,11 @@ Node *CMoveNode::Ideal(PhaseGVN *phase, bool can_reshape) {
phase->type(in(IfTrue)) == Type::TOP) {
return nullptr;
}
Node* progress = TypeNode::Ideal(phase, can_reshape);
if (progress != nullptr) {
return progress;
}
// Check for Min/Max patterns. This is called before constants are pushed to the right input, as that transform can
// make BoolTests non-canonical.
Node* minmax = Ideal_minmax(phase, this);

View File

@ -597,7 +597,6 @@ void Node::setup_is_top() {
//------------------------------~Node------------------------------------------
// Fancy destructor; eagerly attempt to reclaim Node numberings and storage
void Node::destruct(PhaseValues* phase) {
assert(this != Compile::current()->dead_path(), "we want to keep the unique DeadPath node around");
Compile* compile = (phase != nullptr) ? phase->C : Compile::current();
if (phase != nullptr && phase->is_IterGVN()) {
phase->is_IterGVN()->_worklist.remove(this);
@ -736,14 +735,11 @@ void Node::out_grow(uint len) {
//------------------------------is_dead----------------------------------------
bool Node::is_dead() const {
// Mach and pinch point nodes may look like dead.
if (is_top() || is_Mach() || (Opcode() == Op_Node && _outcnt > 0) || this == Compile::current()->dead_path()) {
if( is_top() || is_Mach() || (Opcode() == Op_Node && _outcnt > 0) )
return false;
}
for (uint i = 0; i < _max; i++) {
if (_in[i] != nullptr) {
for( uint i = 0; i < _max; i++ )
if( _in[i] != nullptr )
return false;
}
}
return true;
}
@ -3182,11 +3178,10 @@ uint TypeNode::ideal_reg() const {
return _type->ideal_reg();
}
void Node::make_path_dead(PhaseIterGVN* igvn, PhaseIdealLoop* loop, Node* ctrl_use, uint j, const char* phase_str) {
void TypeNode::make_path_dead(PhaseIterGVN* igvn, PhaseIdealLoop* loop, Node* ctrl_use, uint j, const char* phase_str) {
Node* c = ctrl_use->in(j);
Node* top = igvn->C->top();
if (c != top) {
igvn->replace_input_of(ctrl_use, j, top);
if (igvn->type(c) != Type::TOP) {
igvn->replace_input_of(ctrl_use, j, igvn->C->top());
create_halt_path(igvn, c, loop, phase_str);
}
}
@ -3198,18 +3193,14 @@ void Node::make_path_dead(PhaseIterGVN* igvn, PhaseIdealLoop* loop, Node* ctrl_u
// constant folds and the control flow that leads to the Type node becomes unreachable. There are cases where that
// doesn't happen, however. They are handled here by following uses of the Type node until a CFG or a Phi to find dead
// paths. The dead paths are then replaced by a Halt node.
void Node::make_paths_from_here_dead(PhaseIterGVN* igvn, PhaseIdealLoop* loop, const char* phase_str) {
void TypeNode::make_paths_from_here_dead(PhaseIterGVN* igvn, PhaseIdealLoop* loop, const char* phase_str) {
Unique_Node_List wq;
wq.push(this);
for (uint i = 0; i < wq.size(); ++i) {
Node* n = wq.at(i);
if (n->is_CFG()) {
n->remove_dead_region(igvn, true);
}
for (DUIterator_Fast kmax, k = n->fast_outs(kmax); k < kmax; k++) {
Node* u = n->fast_out(k);
if (u->is_CFG()) {
wq.push(u);
assert(!u->is_Region(), "Can't reach a Region without going through a Phi");
make_path_dead(igvn, loop, u, 0, phase_str);
} else if (u->is_Phi()) {
@ -3229,7 +3220,7 @@ void Node::make_paths_from_here_dead(PhaseIterGVN* igvn, PhaseIdealLoop* loop, c
}
}
void Node::create_halt_path(PhaseIterGVN* igvn, Node* c, PhaseIdealLoop* loop, const char* phase_str) {
void TypeNode::create_halt_path(PhaseIterGVN* igvn, Node* c, PhaseIdealLoop* loop, const char* phase_str) const {
Node* frame = new ParmNode(igvn->C->start(), TypeFunc::FramePtr);
if (loop == nullptr) {
igvn->register_new_node_with_optimizer(frame);
@ -3248,3 +3239,15 @@ void Node::create_halt_path(PhaseIterGVN* igvn, Node* c, PhaseIdealLoop* loop, c
}
igvn->add_input_to(igvn->C->root(), halt);
}
Node* TypeNode::Ideal(PhaseGVN* phase, bool can_reshape) {
if (KillPathsReachableByDeadTypeNode && can_reshape && Value(phase) == Type::TOP) {
PhaseIterGVN* igvn = phase->is_IterGVN();
Node* top = igvn->C->top();
ResourceMark rm;
make_paths_from_here_dead(igvn, nullptr, "igvn");
return top;
}
return Node::Ideal(phase, can_reshape);
}

View File

@ -82,7 +82,6 @@ class CountedLoopEndNode;
class DecodeNarrowPtrNode;
class DecodeNNode;
class DecodeNKlassNode;
class DivModIntegerNode;
class EncodeNarrowPtrNode;
class EncodePNode;
class EncodePKlassNode;
@ -830,9 +829,8 @@ public:
DEFINE_CLASS_ID(LShift, Node, 21)
DEFINE_CLASS_ID(Neg, Node, 22)
DEFINE_CLASS_ID(ReachabilityFence, Node, 23)
DEFINE_CLASS_ID(DivModInteger, Node, 24)
_max_classes = ClassMask_DivModInteger
_max_classes = ClassMask_Neg
};
#undef DEFINE_CLASS_ID
@ -949,7 +947,6 @@ public:
DEFINE_CLASS_QUERY(DecodeNarrowPtr)
DEFINE_CLASS_QUERY(DecodeN)
DEFINE_CLASS_QUERY(DecodeNKlass)
DEFINE_CLASS_QUERY(DivModInteger)
DEFINE_CLASS_QUERY(EncodeNarrowPtr)
DEFINE_CLASS_QUERY(EncodeP)
DEFINE_CLASS_QUERY(EncodePKlass)
@ -1504,10 +1501,6 @@ public:
uint _del_tick; // Bumped when a deletion happens..
#endif
#endif
void make_paths_from_here_dead(PhaseIterGVN* igvn, PhaseIdealLoop* loop, const char* phase_str);
static void create_halt_path(PhaseIterGVN* igvn, Node* c, PhaseIdealLoop* loop, const char* phase_str);
void make_path_dead(PhaseIterGVN* igvn, PhaseIdealLoop* loop, Node* ctrl_use, uint j, const char* phase_str);
};
inline bool not_a_node(const Node* n) {
@ -2205,13 +2198,17 @@ public:
init_class_id(Class_Type);
}
virtual const Type* Value(PhaseGVN* phase) const;
virtual Node* Ideal(PhaseGVN* phase, bool can_reshape);
virtual const Type *bottom_type() const;
virtual uint ideal_reg() const;
void make_path_dead(PhaseIterGVN* igvn, PhaseIdealLoop* loop, Node* ctrl_use, uint j, const char* phase_str);
#ifndef PRODUCT
virtual void dump_spec(outputStream *st) const;
virtual void dump_compact_spec(outputStream *st) const;
#endif
void make_paths_from_here_dead(PhaseIterGVN* igvn, PhaseIdealLoop* loop, const char* phase_str);
void create_halt_path(PhaseIterGVN* igvn, Node* c, PhaseIdealLoop* loop, const char* phase_str) const;
};
#include "opto/opcodes.hpp"

View File

@ -1843,7 +1843,7 @@ void Parse::sharpen_type_after_if(BoolTest::mask btest,
const Type* obj_type = _gvn.type(obj);
const Type* tboth = obj_type->filter_speculative(cast_type);
assert(tboth->higher_equal(obj_type) && tboth->higher_equal(cast_type), "sanity");
if (tboth == Type::TOP && KillPathsReachableByDeadDataNode) {
if (tboth == Type::TOP && KillPathsReachableByDeadTypeNode) {
// Let dead type node cleaning logic prune effectively dead path for us.
// CheckCastPP::Value() == TOP and it will trigger the cleanup during GVN.
// Don't materialize the cast when cleanup is disabled, because

View File

@ -32,7 +32,6 @@
#include "opto/castnode.hpp"
#include "opto/cfgnode.hpp"
#include "opto/convertnode.hpp"
#include "opto/divnode.hpp"
#include "opto/idealGraphPrinter.hpp"
#include "opto/loopnode.hpp"
#include "opto/machnode.hpp"
@ -2198,107 +2197,6 @@ Node *PhaseIterGVN::transform( Node *n ) {
return transform_old(n);
}
DeadPathNode* PhaseIterGVN::dead_path() {
DeadPathNode* dead_path_node = C->dead_path();
if (!dead_path_node->is_active()) {
dead_path_node->activate(this);
}
assert(C->root()->find_edge(dead_path_node) > 0, "should be reachable from root");
return dead_path_node;
}
// If dead_node is a data node, all CFG nodes reachable from dead_node are dead cfg paths. This method follows uses from
// dead_node until it encounters a cfg node or a phi and eagerly kills these dead cfg paths. This is needed because, in
// some corner cases, a data node dies but some data paths that use it (and are unreachable at runtime) are not proven
// dead by igvn, possibly leading to incorrect IR graphs.
// Also see comment at DeadPathNode declaration.
void PhaseIterGVN::make_dependent_paths_dead_if_top(Node* dead_node, const Type* t) {
if (t != Type::TOP) {
return;
}
if (!KillPathsReachableByDeadDataNode) {
return;
}
// dead_node is going dead, follow uses
ResourceMark rm;
Unique_Node_List wq;
wq.push(dead_node);
for (uint i = 0; i < wq.size(); i++) {
Node* n = wq.at(i);
if (n != dead_node && (n->is_Phi() || n->is_CFG())) {
continue;
}
for (DUIterator_Fast kmax, k = n->fast_outs(kmax); k < kmax; k++) {
Node* u = n->fast_out(k);
wq.push(u);
}
}
for (uint i = 0; i < wq.size(); i++) {
Node* n = wq.at(i);
if (n->is_Phi()) {
Node* region = n->in(0);
// Find out through which of the Phi's input, we reached that Phi and mark the corresponding CFG path dead
for (uint j = 1; j < n->req(); j++) {
Node* in = n->in(j);
// We don't follow uses beyond Phis so if 'in' is a Phi (unless it's dead_node), we couldn't reach this Phi through it
if (in == dead_node || (in != nullptr && !in->is_Phi() && wq.member(in))) {
if (!region->is_top() && region->in(j) != nullptr && !region->in(j)->is_top()) {
// We reached this CFG path through data nodes, record it in dead path to later insert a Halt node, if it
// doesn't die in the meantime
dead_path()->add_req(region->in(j));
_worklist.push(dead_path());
replace_input_of(region, j, C->top());
}
replace_input_of(n, j, C->top());
if (in->outcnt() == 0) {
remove_dead_node(in, NodeOrigin::Graph);
}
}
}
continue;
}
if (n == dead_node) {
continue;
}
// We don't want to follow CFG nodes but is_CFG() can return false for a cfg projection if its input is top. So
// there's no foolproof way of telling if dead_node is a cfg or not and as a consequence we can reach a Region.
if (n->is_Region()) {
// Find out through which of the Region's input, we reached that Region and mark it dead
for (uint j = 1; j < n->req(); j++) {
Node* in = n->in(j);
// We don't follow uses beyond Regions so if 'in' is a Region, we couldn't reach this Region through it
if (in != nullptr && !in->is_Region() && wq.member(in)) {
replace_input_of(n, j, C->top());
in->remove_dead_region(this, true);
}
}
continue;
}
// If we reached this CFG node through a data input...
if (n->is_CFG()) {
Node* control_input = n->in(0);
if (control_input != nullptr && !control_input->is_top()) {
// record it in dead path to later insert a Halt node, if it doesn't die in the meantime
dead_path()->add_req(control_input);
_worklist.push(dead_path());
replace_input_of(n, 0, C->top());
}
n->remove_dead_region(this, true);
continue;
}
if (n->outcnt() == 0) {
remove_dead_node(n, NodeOrigin::Graph);
}
}
#ifdef ASSERT
for (uint i = 0; i < wq.size(); i++) {
Node* n = wq.at(i);
assert(n->is_Region() || n->is_Phi() || n->is_CFG() || n->outcnt() == 0, "node should be dead now");
}
#endif
}
Node *PhaseIterGVN::transform_old(Node* n) {
NOT_PRODUCT(set_transforms());
// Remove 'n' from hash table in case it gets modified
@ -2390,7 +2288,6 @@ Node *PhaseIterGVN::transform_old(Node* n) {
}
// If 'k' computes a constant, replace it with a constant
if (t->singleton() && !k->is_Con()) {
make_dependent_paths_dead_if_top(k, t);
set_progress();
Node* con = makecon(t); // Make a constant
add_users_to_worklist(k);
@ -3060,14 +2957,10 @@ void PhaseCCP::analyze_step(Unique_Node_List& worklist, Node* n) {
set_type(n, new_type);
push_child_nodes_to_worklist(worklist, n);
}
if (KillPathsReachableByDeadDataNode && n->is_Type() && new_type == Type::TOP) {
if (KillPathsReachableByDeadTypeNode && n->is_Type() && new_type == Type::TOP) {
// Keep track of Type nodes to kill CFG paths that use Type
// nodes that become dead.
_maybe_top_type_or_div_mod_nodes.push(n);
}
if (KillPathsReachableByDeadDataNode && new_type == Type::TOP && n->is_DivModInteger() &&
type(n->in(2)) == n->as_DivModInteger()->zero()) {
_maybe_top_type_or_div_mod_nodes.push(n);
_maybe_top_type_nodes.push(n);
}
}
@ -3363,16 +3256,16 @@ Node *PhaseCCP::transform( Node *n ) {
// track all visited nodes, so that we can remove the complement
Unique_Node_List useful;
if (KillPathsReachableByDeadDataNode) {
for (uint i = 0; i < _maybe_top_type_or_div_mod_nodes.size(); ++i) {
Node* data_node = _maybe_top_type_or_div_mod_nodes.at(i);
if (type(data_node) == Type::TOP) {
if (KillPathsReachableByDeadTypeNode) {
for (uint i = 0; i < _maybe_top_type_nodes.size(); ++i) {
Node* type_node = _maybe_top_type_nodes.at(i);
if (type(type_node) == Type::TOP) {
ResourceMark rm;
data_node->make_paths_from_here_dead(this, nullptr, "ccp");
type_node->as_Type()->make_paths_from_here_dead(this, nullptr, "ccp");
}
}
} else {
assert(_maybe_top_type_or_div_mod_nodes.size() == 0, "we don't need type nodes");
assert(_maybe_top_type_nodes.size() == 0, "we don't need type nodes");
}
// Initialize the traversal.

View File

@ -501,10 +501,6 @@ protected:
// Usually returns new_type. Returns old_type if new_type is only a slight
// improvement, such that it would take many (>>10) steps to reach 2**32.
DeadPathNode* dead_path();
void make_dependent_paths_dead_if_top(Node* dead_node, const Type* t);
public:
PhaseIterGVN(PhaseIterGVN* igvn); // Used by CCP constructor
@ -699,7 +695,7 @@ protected:
// Should be replaced with combined CCP & GVN someday.
class PhaseCCP : public PhaseIterGVN {
Unique_Node_List _root_and_safepoints;
Unique_Node_List _maybe_top_type_or_div_mod_nodes;
Unique_Node_List _maybe_top_type_nodes;
// Non-recursive. Use analysis to transform single Node.
virtual Node* transform_once(Node* n);

View File

@ -90,48 +90,3 @@ const Type* HaltNode::Value(PhaseGVN* phase) const {
const RegMask &HaltNode::out_RegMask() const {
return RegMask::EMPTY;
}
Node* DeadPathNode::Ideal(PhaseGVN* phase, bool can_reshape) {
assert(unique_ctrl_out() == phase->C->root(), "only referenced from root");
assert(can_reshape, "only used once igvn executes");
bool modified = false;
for (uint i = 1; i < req(); i++) { // For all inputs
// Check for and remove dead inputs
if (phase->type(in(i)) == Type::TOP) {
del_req(i--); // Delete TOP inputs
modified = true;
}
}
if (req() == 1 && is_active()) {
assert(modified, "only if some inputs were removed");
deactivate();
}
return modified ? this : nullptr;
}
const Type* DeadPathNode::Value(PhaseGVN* phase) const {
if (req() == 1) {
return Type::TOP;
}
return bottom_type();
}
void DeadPathNode::activate(PhaseIterGVN* igvn) {
assert(Compile::current()->root()->find_edge(this) < 0, "should be disconnected from root");
set_req(0, this);
// If an entire subgraph died such as with Node::remove_dead_region(), some dead inputs to the DeadPath node will have
// been left behind
while (req() > 1) {
uint last = req() - 1;
assert(in(last) == nullptr || in(last)->is_top(), "only dead inputs should remain");
del_req(last);
}
Node* root_node = Compile::current()->root();
root_node->add_req(this);
igvn->_worklist.push(root_node);
igvn->set_type(this, bottom_type());
}
void DeadPathNode::deactivate() {
set_req(0, nullptr);
}

View File

@ -69,35 +69,4 @@ public:
virtual uint match_edge(uint idx) const { return 0; }
};
// This node collects paths that are found dead by PhaseIterGVN::make_dependent_paths_dead_if_top()
// There is a single DeadPath node for the lifetime of optimizations. It's initially not active (i.e. unreachable from
// the IR graph). When a cfg path becomes dead it's added as an input to the unique DeadPath node. If after some
// optimizations run, the DeadPath node gets disconnected, it's not destroyed. It becomes inactive and can possibly be
// activated again on a subsequent igvn. When optimizations are over, the DeadPath node, if it is active, is expanded to
// a Region and Halt node in Compile::final_graph_reshaping().
// Rather than having this dedicated node, igvn could add a Halt node everytime it finds a dead cfg path from a data
// node. What's likely, however, is that as igvn progresses, that same cfg path is found dead by following cfg edges.
// The Halt node then becomes dead. To avoid this unnecessary cycle of creation of a Halt node only to have it be found
// dead shortly after, dead cfg paths are added to the unique DeadPath node.
class DeadPathNode : public RegionNode {
public:
DeadPathNode() : RegionNode(1) {
deactivate();
assert(Compile::current()->dead_path() == nullptr, "only one");
}
virtual int Opcode() const;
virtual const Type* bottom_type() const { return Type::BOTTOM; }
virtual Node* Identity(PhaseGVN* phase) { return this; }
virtual Node* Ideal(PhaseGVN* phase, bool can_reshape);
virtual const Type* Value(PhaseGVN* phase) const;
bool is_active() const {
return in(0) == this;
}
void activate(PhaseIterGVN* igvn);
void deactivate();
};
#endif // SHARE_OPTO_ROOTNODE_HPP

View File

@ -2391,7 +2391,7 @@ Node* VectorMaskOpNode::Ideal(PhaseGVN* phase, bool can_reshape) {
if (n != nullptr) {
return n;
}
return nullptr;
return TypeNode::Ideal(phase, can_reshape);
}
Node* VectorMaskCastNode::Identity(PhaseGVN* phase) {

View File

@ -1,64 +0,0 @@
/*
* Copyright (c) 2026, 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 8383815
* @summary C2: assert(false) failed: malformed IfNode with 1 outputs
* @run main/othervm -XX:CompileCommand=compileonly,${test.main.class}*::* -XX:-TieredCompilation -Xbatch -XX:PerMethodTrapLimit=0 ${test.main.class}
* @run main ${test.main.class}
*/
package compiler.integerArithmetic;
public class TestDivByZeroInLiveCFGPath {
static long lFld;
static int iArr[] = new int[400];
public static void main(String[] strArr) {
for (int i = 0; i < 10; i++) {
test();
}
}
static void test() {
int x;
for (int i = 9; i < 100; ++i) {
int j = 100;
while (--j > 0) {
iArr[1] = (int) lFld;
}
try {
iArr[1] = (5 / j);
x = (i / iArr[8]);
} catch (ArithmeticException a_e) {
}
}
for (int i = 18; i < 50; i++) {
iArr[2] += lFld;
}
}
}

View File

@ -27,19 +27,17 @@
* @summary [REDO] C2: crash in compiled code because of dependency on removed range check CastIIs
*
* @run main/othervm -XX:-TieredCompilation -XX:-UseOnStackReplacement -XX:-BackgroundCompilation
* -XX:CompileCommand=dontinline,${test.main.class}::notInlined
* ${test.main.class}
* -XX:CompileCommand=dontinline,TestArrayAccessAboveRCAfterRCCastIIEliminated::notInlined
* TestArrayAccessAboveRCAfterRCCastIIEliminated
* @run main/othervm -XX:-TieredCompilation -XX:-UseOnStackReplacement -XX:-BackgroundCompilation
* -XX:CompileCommand=dontinline,${test.main.class}::notInlined
* -XX:+UnlockDiagnosticVMOptions -XX:+StressGCM ${test.main.class}
* @run main ${test.main.class}
* @run main/othervm -XX:CompileCommand=dontinline,${test.main.class}::notInlined
* ${test.main.class}
* -XX:CompileCommand=dontinline,TestArrayAccessAboveRCAfterRCCastIIEliminated::notInlined
* -XX:+UnlockDiagnosticVMOptions -XX:+StressGCM TestArrayAccessAboveRCAfterRCCastIIEliminated
* @run main TestArrayAccessAboveRCAfterRCCastIIEliminated
* @run main/othervm -XX:CompileCommand=dontinline,TestArrayAccessAboveRCAfterRCCastIIEliminated::notInlined
* TestArrayAccessAboveRCAfterRCCastIIEliminated
*
*/
package compiler.rangechecks;
public class TestArrayAccessAboveRCAfterRCCastIIEliminated {
private static int intField;
private static long longField;
@ -484,7 +482,7 @@ public class TestArrayAccessAboveRCAfterRCCastIIEliminated {
}
}
// Widened range check cast type after loop opts causes control dependency to be lost
// Range check cast type widen after loop opts causes control dependency to be lost
private static void test14(int i, int j, int flag, boolean flag2) {
int l = 0;
for (; l < 10; l++);
@ -492,14 +490,12 @@ public class TestArrayAccessAboveRCAfterRCCastIIEliminated {
int[] array = new int[10];
notInlined(array);
if (flag == 0) {
throw new RuntimeException("never taken");
}
if (flag2) {
float[] newArray = new float[10];
newArray[i+j] = 42; // i+j in [0, 9]
float[] otherArray = new float[i+j]; // i+j in [0, max]
if (flag == 0) {
throw new RuntimeException("never taken");
}
intField = array[otherArray.length];
} else {
@ -507,7 +503,6 @@ public class TestArrayAccessAboveRCAfterRCCastIIEliminated {
newArray[i+j] = 42; // i+j in [0, 9]
float[] otherArray = new float[i+j]; // i+j in [0, max]
if (flag == 0) {
throw new RuntimeException("never taken");
}
intField = array[otherArray.length];
}
@ -528,14 +523,12 @@ public class TestArrayAccessAboveRCAfterRCCastIIEliminated {
int[] array = new int[10];
notInlined(array);
if (flag == 0) {
throw new RuntimeException("never taken");
}
if (flag2) {
float[] newArray = new float[10];
newArray[i+j] = 42; // i+j in [0, 9]
float[] otherArray = new float[i+j]; // i+j in [0, max]
if (flag == 0) {
throw new RuntimeException("never taken");
}
intField = array[otherArray.length];
} else {
@ -543,7 +536,6 @@ public class TestArrayAccessAboveRCAfterRCCastIIEliminated {
newArray[i+j] = 42; // i+j in [0, 9]
float[] otherArray = new float[i+j]; // i+j in [0, max]
if (flag == 0) {
throw new RuntimeException("never taken");
}
intField = array[otherArray.length];
}