From d6899460c7ca5daf402258e5d3ccb399a5607d3a Mon Sep 17 00:00:00 2001 From: Roland Westrelin Date: Wed, 15 Jul 2026 09:25:58 +0000 Subject: [PATCH] 8380166: C2: crash in compiled code due to zero division because of widened CastII Reviewed-by: qamai, chagedorn --- src/hotspot/share/opto/c2_globals.hpp | 4 +- src/hotspot/share/opto/castnode.cpp | 3 - src/hotspot/share/opto/cfgnode.cpp | 14 +- src/hotspot/share/opto/classes.hpp | 1 + src/hotspot/share/opto/compile.cpp | 25 +- src/hotspot/share/opto/compile.hpp | 9 +- src/hotspot/share/opto/convertnode.cpp | 14 - src/hotspot/share/opto/divnode.cpp | 14 + src/hotspot/share/opto/divnode.hpp | 13 +- src/hotspot/share/opto/loopopts.cpp | 2 +- src/hotspot/share/opto/movenode.cpp | 5 - src/hotspot/share/opto/node.cpp | 37 +- src/hotspot/share/opto/node.hpp | 13 +- src/hotspot/share/opto/parse2.cpp | 2 +- src/hotspot/share/opto/phaseX.cpp | 123 +- src/hotspot/share/opto/phaseX.hpp | 6 +- src/hotspot/share/opto/rootnode.cpp | 45 + src/hotspot/share/opto/rootnode.hpp | 31 + src/hotspot/share/opto/vectornode.cpp | 2 +- .../c2/TestDeadPathManyDeadDataNodes.java | 1301 +++++++++++++++++ .../TestDivByZeroInLiveCFGPath.java | 64 + .../TestZeroDivModWidenedCastII.java | 1122 ++++++++++++++ ...yAccessAboveRCAfterRCCastIIEliminated.java | 24 +- 23 files changed, 2793 insertions(+), 81 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/c2/TestDeadPathManyDeadDataNodes.java create mode 100644 test/hotspot/jtreg/compiler/integerArithmetic/TestDivByZeroInLiveCFGPath.java create mode 100644 test/hotspot/jtreg/compiler/integerArithmetic/TestZeroDivModWidenedCastII.java diff --git a/src/hotspot/share/opto/c2_globals.hpp b/src/hotspot/share/opto/c2_globals.hpp index 9ff88e8c310..dd9288f7617 100644 --- a/src/hotspot/share/opto/c2_globals.hpp +++ b/src/hotspot/share/opto/c2_globals.hpp @@ -922,8 +922,8 @@ "Use StoreStore barrier instead of Release barrier at the end " \ "of constructors") \ \ - develop(bool, KillPathsReachableByDeadTypeNode, true, \ - "When a Type node becomes top, make paths where the node is " \ + develop(bool, KillPathsReachableByDeadDataNode, true, \ + "When a data 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.") \ diff --git a/src/hotspot/share/opto/castnode.cpp b/src/hotspot/share/opto/castnode.cpp index 7bb6b1dcb77..076a95acfd8 100644 --- a/src/hotspot/share/opto/castnode.cpp +++ b/src/hotspot/share/opto/castnode.cpp @@ -111,9 +111,6 @@ 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; } diff --git a/src/hotspot/share/opto/cfgnode.cpp b/src/hotspot/share/opto/cfgnode.cpp index 828e5bf299f..ed5da046608 100644 --- a/src/hotspot/share/opto/cfgnode.cpp +++ b/src/hotspot/share/opto/cfgnode.cpp @@ -693,14 +693,13 @@ Node *RegionNode::Ideal(PhaseGVN *phase, bool can_reshape) { if (add_to_worklist) { igvn->add_users_to_worklist(this); // Check for further allowed opts } - for (DUIterator_Last imin, i = last_outs(imin); i >= imin; --i) { + uint edges_removed; + for (DUIterator_Last imin, i = last_outs(imin); i >= imin; i -= edges_removed) { + edges_removed = 1; Node* n = last_out(i); igvn->hash_delete(n); // Remove from worklist before modifying edges if (n->outcnt() == 0) { - 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); - } + edges_removed = n->replace_edge(this, phase->C->top(), igvn); continue; } if( n->is_Phi() ) { // Collapse all Phis @@ -719,10 +718,7 @@ 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"); - 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); - } + edges_removed = n->replace_edge(this, parent_ctrl, igvn); } else { assert(n->in(0) == this, "Expect RegionNode to be control parent"); diff --git a/src/hotspot/share/opto/classes.hpp b/src/hotspot/share/opto/classes.hpp index 53a72f979db..c296237de37 100644 --- a/src/hotspot/share/opto/classes.hpp +++ b/src/hotspot/share/opto/classes.hpp @@ -121,6 +121,7 @@ macro(CompareAndExchangeI) macro(CompareAndExchangeL) macro(CompareAndExchangeP) macro(CompareAndExchangeN) +macro(DeadPath) macro(GetAndAddB) macro(GetAndAddS) macro(GetAndAddI) diff --git a/src/hotspot/share/opto/compile.cpp b/src/hotspot/share/opto/compile.cpp index 93d8e4c425d..db43c6fb1c4 100644 --- a/src/hotspot/share/opto/compile.cpp +++ b/src/hotspot/share/opto/compile.cpp @@ -312,6 +312,8 @@ 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"); @@ -388,7 +390,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()) { + if (!dead->is_Con() && dead != dead_path()) { record_dead_node(dead->_idx); } if (dead->is_macro()) { @@ -684,6 +686,7 @@ 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), @@ -754,6 +757,7 @@ Compile::Compile(ciEnv* ci_env, ciMethod* target, int osr_bci, } Init(/*do_aliasing=*/ true); + set_dead_path(new DeadPathNode()); print_compile_messages(); @@ -963,6 +967,7 @@ 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), @@ -2630,6 +2635,9 @@ 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();) } @@ -3938,6 +3946,21 @@ 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(), ""); diff --git a/src/hotspot/share/opto/compile.hpp b/src/hotspot/share/opto/compile.hpp index ab36f59a28f..73e136787f8 100644 --- a/src/hotspot/share/opto/compile.hpp +++ b/src/hotspot/share/opto/compile.hpp @@ -57,6 +57,7 @@ class CallStaticJavaNode; class CloneMap; class CompilationFailureInfo; class ConnectionGraph; +class DeadPathNode; class IdealGraphPrinter; class InlineTree; class Matcher; @@ -427,7 +428,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; @@ -897,6 +898,12 @@ 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(); diff --git a/src/hotspot/share/opto/convertnode.cpp b/src/hotspot/share/opto/convertnode.cpp index a495814da61..d706a13feb3 100644 --- a/src/hotspot/share/opto/convertnode.cpp +++ b/src/hotspot/share/opto/convertnode.cpp @@ -755,13 +755,6 @@ 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 @@ -864,13 +857,6 @@ 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 ) { diff --git a/src/hotspot/share/opto/divnode.cpp b/src/hotspot/share/opto/divnode.cpp index 1687ff2cade..3b51491294e 100644 --- a/src/hotspot/share/opto/divnode.cpp +++ b/src/hotspot/share/opto/divnode.cpp @@ -1031,6 +1031,10 @@ 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; @@ -1067,6 +1071,10 @@ 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; @@ -1380,6 +1388,9 @@ 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(phase, this); } @@ -1520,6 +1531,9 @@ 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(phase, this); } diff --git a/src/hotspot/share/opto/divnode.hpp b/src/hotspot/share/opto/divnode.hpp index 366e3fb882d..de89dcaad06 100644 --- a/src/hotspot/share/opto/divnode.hpp +++ b/src/hotspot/share/opto/divnode.hpp @@ -40,7 +40,9 @@ private: bool _pinned; protected: - DivModIntegerNode(Node* c, Node* dividend, Node* divisor) : Node(c, dividend, divisor), _pinned(false) {} + DivModIntegerNode(Node* c, Node* dividend, Node* divisor) : Node(c, dividend, divisor), _pinned(false) { + init_class_id(Class_DivModInteger); + } private: virtual uint size_of() const override { return sizeof(DivModIntegerNode); } @@ -52,6 +54,15 @@ 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--------------------------------------- diff --git a/src/hotspot/share/opto/loopopts.cpp b/src/hotspot/share/opto/loopopts.cpp index ccd53129a87..d525c274ef6 100644 --- a/src/hotspot/share/opto/loopopts.cpp +++ b/src/hotspot/share/opto/loopopts.cpp @@ -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 && - (KillPathsReachableByDeadTypeNode || !n->is_Type()) + (KillPathsReachableByDeadDataNode || !n->is_Type()) ) { Node *n_ctrl = get_ctrl(n); IdealLoopTree *n_loop = get_loop(n_ctrl); diff --git a/src/hotspot/share/opto/movenode.cpp b/src/hotspot/share/opto/movenode.cpp index 6b6becb434f..7d38238da2f 100644 --- a/src/hotspot/share/opto/movenode.cpp +++ b/src/hotspot/share/opto/movenode.cpp @@ -90,11 +90,6 @@ 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); diff --git a/src/hotspot/share/opto/node.cpp b/src/hotspot/share/opto/node.cpp index 726a3ea1b55..264216ddc6d 100644 --- a/src/hotspot/share/opto/node.cpp +++ b/src/hotspot/share/opto/node.cpp @@ -597,6 +597,7 @@ 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); @@ -735,11 +736,14 @@ 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) ) + if (is_top() || is_Mach() || (Opcode() == Op_Node && _outcnt > 0) || this == Compile::current()->dead_path()) { 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; } @@ -3178,10 +3182,11 @@ uint TypeNode::ideal_reg() const { return _type->ideal_reg(); } -void TypeNode::make_path_dead(PhaseIterGVN* igvn, PhaseIdealLoop* loop, Node* ctrl_use, uint j, const char* phase_str) { +void Node::make_path_dead(PhaseIterGVN* igvn, PhaseIdealLoop* loop, Node* ctrl_use, uint j, const char* phase_str) { Node* c = ctrl_use->in(j); - if (igvn->type(c) != Type::TOP) { - igvn->replace_input_of(ctrl_use, j, igvn->C->top()); + Node* top = igvn->C->top(); + if (c != top) { + igvn->replace_input_of(ctrl_use, j, top); create_halt_path(igvn, c, loop, phase_str); } } @@ -3193,14 +3198,18 @@ void TypeNode::make_path_dead(PhaseIterGVN* igvn, PhaseIdealLoop* loop, Node* ct // 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 TypeNode::make_paths_from_here_dead(PhaseIterGVN* igvn, PhaseIdealLoop* loop, const char* phase_str) { +void Node::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()) { @@ -3220,7 +3229,7 @@ void TypeNode::make_paths_from_here_dead(PhaseIterGVN* igvn, PhaseIdealLoop* loo } } -void TypeNode::create_halt_path(PhaseIterGVN* igvn, Node* c, PhaseIdealLoop* loop, const char* phase_str) const { +void Node::create_halt_path(PhaseIterGVN* igvn, Node* c, PhaseIdealLoop* loop, const char* phase_str) { Node* frame = new ParmNode(igvn->C->start(), TypeFunc::FramePtr); if (loop == nullptr) { igvn->register_new_node_with_optimizer(frame); @@ -3239,15 +3248,3 @@ void TypeNode::create_halt_path(PhaseIterGVN* igvn, Node* c, PhaseIdealLoop* loo } 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); -} diff --git a/src/hotspot/share/opto/node.hpp b/src/hotspot/share/opto/node.hpp index b3de7498e50..e593822c313 100644 --- a/src/hotspot/share/opto/node.hpp +++ b/src/hotspot/share/opto/node.hpp @@ -82,6 +82,7 @@ class CountedLoopEndNode; class DecodeNarrowPtrNode; class DecodeNNode; class DecodeNKlassNode; +class DivModIntegerNode; class EncodeNarrowPtrNode; class EncodePNode; class EncodePKlassNode; @@ -829,8 +830,9 @@ 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_Neg + _max_classes = ClassMask_DivModInteger }; #undef DEFINE_CLASS_ID @@ -947,6 +949,7 @@ 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) @@ -1501,6 +1504,10 @@ 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) { @@ -2198,17 +2205,13 @@ 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" diff --git a/src/hotspot/share/opto/parse2.cpp b/src/hotspot/share/opto/parse2.cpp index 9cb20cfcd00..6e58fae51e1 100644 --- a/src/hotspot/share/opto/parse2.cpp +++ b/src/hotspot/share/opto/parse2.cpp @@ -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 && KillPathsReachableByDeadTypeNode) { + if (tboth == Type::TOP && KillPathsReachableByDeadDataNode) { // 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 diff --git a/src/hotspot/share/opto/phaseX.cpp b/src/hotspot/share/opto/phaseX.cpp index a4d6a6c33d0..c124f940a27 100644 --- a/src/hotspot/share/opto/phaseX.cpp +++ b/src/hotspot/share/opto/phaseX.cpp @@ -32,6 +32,7 @@ #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" @@ -2197,6 +2198,107 @@ 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 @@ -2288,6 +2390,7 @@ 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); @@ -2957,10 +3060,14 @@ void PhaseCCP::analyze_step(Unique_Node_List& worklist, Node* n) { set_type(n, new_type); push_child_nodes_to_worklist(worklist, n); } - if (KillPathsReachableByDeadTypeNode && n->is_Type() && new_type == Type::TOP) { + if (KillPathsReachableByDeadDataNode && 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_nodes.push(n); + _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); } } @@ -3256,16 +3363,16 @@ Node *PhaseCCP::transform( Node *n ) { // track all visited nodes, so that we can remove the complement Unique_Node_List useful; - 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) { + 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) { ResourceMark rm; - type_node->as_Type()->make_paths_from_here_dead(this, nullptr, "ccp"); + data_node->make_paths_from_here_dead(this, nullptr, "ccp"); } } } else { - assert(_maybe_top_type_nodes.size() == 0, "we don't need type nodes"); + assert(_maybe_top_type_or_div_mod_nodes.size() == 0, "we don't need type nodes"); } // Initialize the traversal. diff --git a/src/hotspot/share/opto/phaseX.hpp b/src/hotspot/share/opto/phaseX.hpp index 014d16f92f6..7ea7aa99142 100644 --- a/src/hotspot/share/opto/phaseX.hpp +++ b/src/hotspot/share/opto/phaseX.hpp @@ -501,6 +501,10 @@ 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 @@ -695,7 +699,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_nodes; + Unique_Node_List _maybe_top_type_or_div_mod_nodes; // Non-recursive. Use analysis to transform single Node. virtual Node* transform_once(Node* n); diff --git a/src/hotspot/share/opto/rootnode.cpp b/src/hotspot/share/opto/rootnode.cpp index 60167c5436a..1e5ef29e79c 100644 --- a/src/hotspot/share/opto/rootnode.cpp +++ b/src/hotspot/share/opto/rootnode.cpp @@ -90,3 +90,48 @@ 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); +} diff --git a/src/hotspot/share/opto/rootnode.hpp b/src/hotspot/share/opto/rootnode.hpp index 76f0ec440a9..61ad317d455 100644 --- a/src/hotspot/share/opto/rootnode.hpp +++ b/src/hotspot/share/opto/rootnode.hpp @@ -69,4 +69,35 @@ 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 diff --git a/src/hotspot/share/opto/vectornode.cpp b/src/hotspot/share/opto/vectornode.cpp index 20857eed35c..60eda1204b7 100644 --- a/src/hotspot/share/opto/vectornode.cpp +++ b/src/hotspot/share/opto/vectornode.cpp @@ -2391,7 +2391,7 @@ Node* VectorMaskOpNode::Ideal(PhaseGVN* phase, bool can_reshape) { if (n != nullptr) { return n; } - return TypeNode::Ideal(phase, can_reshape); + return nullptr; } Node* VectorMaskCastNode::Identity(PhaseGVN* phase) { diff --git a/test/hotspot/jtreg/compiler/c2/TestDeadPathManyDeadDataNodes.java b/test/hotspot/jtreg/compiler/c2/TestDeadPathManyDeadDataNodes.java new file mode 100644 index 00000000000..e9c5a8f7529 --- /dev/null +++ b/test/hotspot/jtreg/compiler/c2/TestDeadPathManyDeadDataNodes.java @@ -0,0 +1,1301 @@ +/* + * Copyright (c) 2026 IBM Corporation. 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 8380166 + * @summary C2: crash in compiled code due to zero division because of widened CastII + * @run main/othervm -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions + * -Xcomp -XX:CompileOnly=TestDeadPathManyDeadDataNodes::test1 + * -XX:CompileCommand=quiet + * -XX:CompileCommand=inline,TestDeadPathManyDeadDataNodes::inlined1 + * -XX:MaxRecursiveInlineLevel=1000 -XX:MaxInlineLevel=1000 + * -XX:-TieredCompilation -XX:+AlwaysIncrementalInline + * -XX:+DelayAfterInliningCutoff -XX:+IncrementalInlineForceCleanup + * -XX:NodeCountInliningCutoff=100000 -XX:+StressIGVN + * ${test.main.class} + * @run main ${test.main.class} + */ + +package compiler.c2; + +public class TestDeadPathManyDeadDataNodes { + private static int field; + private static boolean boolField2; + private static int arrayLengthField; + + public static void main(String[] args) { + Object o = new Object(); + try { + test1(false, 0); + } catch (NegativeArraySizeException nase) { + } + } + + private static int test1(boolean boolParam, int intParam) { + int length; + int res = 0; + length = -1; + for (int i = 0; i < 2; i++) { + if (boolParam) { + field = 42; + } + int[] array = new int[length]; + arrayLengthField = array.length; + while(true) { + Object o = new Object(); + int arrayLength = arrayLengthField; + arrayLengthField = 0; + switch (intParam) { + case 0: + if (boolField2) { + break; + } + field = 42; + continue; + case 1: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 2: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 3: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 4: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 5: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 6: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 7: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 8: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 9: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 10: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 11: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 12: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 13: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 14: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 15: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 16: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 17: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 18: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 19: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 20: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 21: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 22: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 23: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 24: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 25: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 26: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 27: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 28: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 29: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 30: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 31: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 32: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 33: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 34: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 35: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 36: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 37: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 38: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 39: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 40: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 41: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 42: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 43: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 44: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 45: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 46: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 47: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 48: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 49: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 50: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 51: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 52: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 53: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 54: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 55: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 56: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 57: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 58: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 59: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 60: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 61: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 62: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 63: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 64: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 65: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 66: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 67: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 68: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 69: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 70: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 71: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 72: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 73: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 74: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 75: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 76: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 77: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 78: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 79: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 80: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 81: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 82: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 83: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 84: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 85: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 86: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 87: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 88: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 89: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 90: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 91: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 92: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 93: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 94: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 95: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 96: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 97: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 98: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 99: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + default: + res += inlined1(boolParam, intParam/100, arrayLength, 92); + continue; + } + field = 42; + break; + } + length = lastInlined(); + } + return res; + } + + static int lastInlined() { + return -1; + } + + static int inlined1(boolean boolParam, int intParam, int arrayLength, int count) { + int res = 0; + switch (intParam) { + case 0: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 1: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 2: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 3: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 4: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 5: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 6: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 7: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 8: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 9: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 10: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 11: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 12: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 13: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 14: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 15: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 16: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 17: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 18: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 19: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 20: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 21: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 22: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 23: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 24: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 25: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 26: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 27: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 28: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 29: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 30: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 31: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 32: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 33: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 34: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 35: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 36: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 37: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 38: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 39: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 40: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 41: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 42: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 43: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 44: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 45: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 46: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 47: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 48: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 49: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 50: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 51: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 52: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 53: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 54: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 55: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 56: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 57: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 58: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 59: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 60: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 61: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 62: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 63: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 64: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 65: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 66: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 67: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 68: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 69: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 70: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 71: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 72: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 73: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 74: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 75: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 76: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 77: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 78: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 79: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 80: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 81: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 82: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 83: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 84: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 85: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 86: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 87: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 88: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 89: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 90: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 91: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 92: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 93: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 94: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 95: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 96: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 97: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 98: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 99: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + default: + if (count == 0) { + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + } else { + return inlined1(boolParam, intParam / 100, arrayLength, count-1); + } + } + } +} diff --git a/test/hotspot/jtreg/compiler/integerArithmetic/TestDivByZeroInLiveCFGPath.java b/test/hotspot/jtreg/compiler/integerArithmetic/TestDivByZeroInLiveCFGPath.java new file mode 100644 index 00000000000..6eaf3d86e71 --- /dev/null +++ b/test/hotspot/jtreg/compiler/integerArithmetic/TestDivByZeroInLiveCFGPath.java @@ -0,0 +1,64 @@ + +/* + * 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; + } + } +} + diff --git a/test/hotspot/jtreg/compiler/integerArithmetic/TestZeroDivModWidenedCastII.java b/test/hotspot/jtreg/compiler/integerArithmetic/TestZeroDivModWidenedCastII.java new file mode 100644 index 00000000000..a5bc8fc9287 --- /dev/null +++ b/test/hotspot/jtreg/compiler/integerArithmetic/TestZeroDivModWidenedCastII.java @@ -0,0 +1,1122 @@ +/* + * Copyright (c) 2026 IBM Corporation. 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 8380166 + * @summary C2: crash in compiled code due to zero division because of widened CastII + * + * @run main/othervm -XX:-TieredCompilation -XX:-UseOnStackReplacement -XX:-BackgroundCompilation + * ${test.main.class} + * @run main ${test.main.class} + * + */ + +package compiler.integerArithmetic; + +public class TestZeroDivModWidenedCastII { + private static int intField; + private static long longField; + private static volatile int volatileField; + + public static void main(String[] args) { + for (int i = 0; i < 20_000; i++) { + test1(0, 9, 1, true, false); + test1(0, 9, 1, false, false); + inlined1_2(9, 1, 1, true, 0); + inlined1_3(0, 0); + test2(0, 9, 1, true, false); + test2(0, 9, 1, false, false); + inlined2_2(9, 1, 1, true, 0); + inlined2_3(0, 0); + test3(0, 9, 1, true, false); + test3(0, 9, 1, false, false); + inlined3_2(9, 1, 1, true, 0); + inlined3_3(0, 0); + test4(0, 9, 1, true, false); + test4(0, 9, 1, false, false); + inlined4_2(9, 1, 1, true, 0); + inlined4_3(0, 0); + test5(0, 9, 1, true, false); + test5(0, 9, 1, false, false); + inlined5_2(9, 1, 1, true, 0); + inlined5_3(0, 0); + test6(0, 9, 1, true, false); + test6(0, 9, 1, false, false); + inlined6_2(9, 1, 1, true, 0); + inlined6_3(0, 0); + test7(0, 9, 1, true, false); + test7(0, 9, 1, false, false); + inlined7_2(9, 1, 1, true, 0); + inlined7_3(0, 0); + test8(0, 9, 1, true, false); + test8(0, 9, 1, false, false); + inlined8_2(9, 1, 1, true, 0); + inlined8_3(0, 0); + test9(0, 9, 1, true, false); + test9(0, 9, 1, false, false); + inlined9_2(9, 1, 1, true, 0); + inlined9_3(0, 0); + test10(0, 9, 1, false); + inlined10_2(9, 1, 1, true, 0); + inlined10_3(0, 0); + test11(0, 9, 1, false); + inlined11_2(9, 1, 1, true, 0); + inlined11_3(0, 0); + test12(0, 9, 1, false); + inlined12_2(9, 1, 1, true, 0); + inlined12_3(0, 0); + test13(0, 9, 1, false); + inlined13_2(9, 1, 1, true, 0); + inlined13_3(0, 0); + test14(0, 9, 1, false); + inlined14_2(9, 1, 1, true, 0); + inlined14_3(0, 0); + test15(0, 9, 1, false); + inlined15_2(9, 1, 1, true, 0); + inlined15_3(0, 0); + test16(0, 9, 1, false); + inlined16_2(9, 1, 1, true, 0); + inlined16_3(0, 0); + test17(0, 9, 1, false); + inlined17_2(9, 1, 1, true, 0); + inlined17_3(0, 0); + } + } + + private static void test1(int k, int j, int flag, boolean flag2, boolean flag3) { + int l = 0; + for (; l < 10; l++); + int m = inlined1_3(j, l); + + int i = inlined1(k, flag2); + j = Integer.min(j, 9); + int[] array = new int[10]; + if (flag == 0) { + throw new RuntimeException("never taken"); + } + if (flag2) { + inlined1_2(j, flag, i, flag3, m); + } else { + inlined1_2(j, flag, i, flag3, m); + } + } + + private static int inlined1_3(int j, int l) { + if (l == 10) { + j = 1; + } + return j; + } + + private static void inlined1_2(int j, int flag, int i, boolean flag3, int m) { + if (flag3) { + float[] newArray = new float[j + 1]; // j + 1 in [0..10] + // RC i