8354282: C2: more crashes in compiled code because of dependency on removed range check CastIIs

Reviewed-by: chagedorn
Backport-of: 00068a80304a809297d0df8698850861e9a1c5e9
This commit is contained in:
Roland Westrelin 2026-01-05 14:46:49 +00:00
parent 6950503dcf
commit d8a1c1d04c
13 changed files with 341 additions and 102 deletions

View File

@ -1394,7 +1394,7 @@ void ShenandoahBarrierC2Support::pin_and_expand(PhaseIdealLoop* phase) {
}
if (addr->Opcode() == Op_AddP) {
Node* orig_base = addr->in(AddPNode::Base);
Node* base = new CheckCastPPNode(ctrl, orig_base, orig_base->bottom_type(), ConstraintCastNode::StrongDependency);
Node* base = new CheckCastPPNode(ctrl, orig_base, orig_base->bottom_type(), ConstraintCastNode::DependencyType::NonFloatingNarrowing);
phase->register_new_node(base, ctrl);
if (addr->in(AddPNode::Base) == addr->in((AddPNode::Address))) {
// Field access

View File

@ -22,7 +22,6 @@
*
*/
#include "castnode.hpp"
#include "opto/addnode.hpp"
#include "opto/callnode.hpp"
#include "opto/castnode.hpp"
@ -35,12 +34,22 @@
#include "opto/type.hpp"
#include "utilities/checkedCast.hpp"
const ConstraintCastNode::DependencyType ConstraintCastNode::DependencyType::FloatingNarrowing(true, true, "floating narrowing dependency"); // not pinned, narrows type
const ConstraintCastNode::DependencyType ConstraintCastNode::DependencyType::FloatingNonNarrowing(true, false, "floating non-narrowing dependency"); // not pinned, doesn't narrow type
const ConstraintCastNode::DependencyType ConstraintCastNode::DependencyType::NonFloatingNarrowing(false, true, "non-floating narrowing dependency"); // pinned, narrows type
const ConstraintCastNode::DependencyType ConstraintCastNode::DependencyType::NonFloatingNonNarrowing(false, false, "non-floating non-narrowing dependency"); // pinned, doesn't narrow type
//=============================================================================
// If input is already higher or equal to cast type, then this is an identity.
Node* ConstraintCastNode::Identity(PhaseGVN* phase) {
if (_dependency == UnconditionalDependency) {
if (!_dependency.narrows_type()) {
// If this cast doesn't carry a type dependency (i.e. not used for type narrowing), we cannot optimize it.
return this;
}
// This cast node carries a type dependency. We can remove it if:
// - Its input has a narrower type
// - There's a dominating cast with same input but narrower type
Node* dom = dominating_cast(phase, phase);
if (dom != nullptr) {
return dom;
@ -109,7 +118,7 @@ Node* ConstraintCastNode::Ideal(PhaseGVN* phase, bool can_reshape) {
}
uint ConstraintCastNode::hash() const {
return TypeNode::hash() + (int)_dependency + (_extra_types != nullptr ? _extra_types->hash() : 0);
return TypeNode::hash() + _dependency.hash() + (_extra_types != nullptr ? _extra_types->hash() : 0);
}
bool ConstraintCastNode::cmp(const Node &n) const {
@ -117,7 +126,7 @@ bool ConstraintCastNode::cmp(const Node &n) const {
return false;
}
ConstraintCastNode& cast = (ConstraintCastNode&) n;
if (cast._dependency != _dependency) {
if (!cast._dependency.cmp(_dependency)) {
return false;
}
if (_extra_types == nullptr || cast._extra_types == nullptr) {
@ -130,7 +139,7 @@ uint ConstraintCastNode::size_of() const {
return sizeof(*this);
}
Node* ConstraintCastNode::make_cast_for_basic_type(Node* c, Node* n, const Type* t, DependencyType dependency, BasicType bt) {
Node* ConstraintCastNode::make_cast_for_basic_type(Node* c, Node* n, const Type* t, const DependencyType& dependency, BasicType bt) {
switch(bt) {
case T_INT:
return new CastIINode(c, n, t, dependency);
@ -143,9 +152,9 @@ Node* ConstraintCastNode::make_cast_for_basic_type(Node* c, Node* n, const Type*
}
TypeNode* ConstraintCastNode::dominating_cast(PhaseGVN* gvn, PhaseTransform* pt) const {
if (_dependency == UnconditionalDependency) {
return nullptr;
}
// See discussion at definition of ConstraintCastNode::DependencyType: replacing this cast with a dominating one is
// not safe if _dependency.narrows_type() is not true.
assert(_dependency.narrows_type(), "cast can't be replaced by dominating one");
Node* val = in(1);
Node* ctl = in(0);
int opc = Opcode();
@ -205,30 +214,21 @@ void ConstraintCastNode::dump_spec(outputStream *st) const {
st->print(" extra types: ");
_extra_types->dump_on(st);
}
if (_dependency != RegularDependency) {
st->print(" %s dependency", _dependency == StrongDependency ? "strong" : "unconditional");
}
st->print(" ");
_dependency.dump_on(st);
}
#endif
const Type* CastIINode::Value(PhaseGVN* phase) const {
const Type *res = ConstraintCastNode::Value(phase);
if (res == Type::TOP) {
return Type::TOP;
}
assert(res->isa_int(), "res must be int");
// Similar to ConvI2LNode::Value() for the same reasons
// see if we can remove type assertion after loop opts
res = widen_type(phase, res, T_INT);
return res;
CastIINode* CastIINode::make_with(Node* parent, const TypeInteger* type, const DependencyType& dependency) const {
return new CastIINode(in(0), parent, type, dependency, _range_check_dependency, _extra_types);
}
Node* ConstraintCastNode::find_or_make_integer_cast(PhaseIterGVN* igvn, Node* parent, const TypeInteger* type) const {
Node* n = clone();
n->set_req(1, parent);
n->as_ConstraintCast()->set_type(type);
CastLLNode* CastLLNode::make_with(Node* parent, const TypeInteger* type, const DependencyType& dependency) const {
return new CastLLNode(in(0), parent, type, dependency, _extra_types);
}
Node* ConstraintCastNode::find_or_make_integer_cast(PhaseIterGVN* igvn, Node* parent, const TypeInteger* type, const DependencyType& dependency) const {
Node* n = make_with(parent, type, dependency);
Node* existing = igvn->hash_find_insert(n);
if (existing != nullptr) {
n->destruct(igvn);
@ -242,14 +242,13 @@ Node *CastIINode::Ideal(PhaseGVN *phase, bool can_reshape) {
if (progress != nullptr) {
return progress;
}
if (can_reshape && !phase->C->post_loop_opts_phase()) {
// makes sure we run ::Value to potentially remove type assertion after loop opts
if (!phase->C->post_loop_opts_phase()) {
// makes sure we run widen_type() to potentially common type assertions after loop opts
phase->C->record_for_post_loop_opts_igvn(this);
}
if (!_range_check_dependency || phase->C->post_loop_opts_phase()) {
return optimize_integer_cast(phase, T_INT);
}
phase->C->record_for_post_loop_opts_igvn(this);
return nullptr;
}
@ -279,9 +278,9 @@ void CastIINode::dump_spec(outputStream* st) const {
#endif
CastIINode* CastIINode::pin_array_access_node() const {
assert(_dependency == RegularDependency, "already pinned");
assert(_dependency.is_floating(), "already pinned");
if (has_range_check()) {
return new CastIINode(in(0), in(1), bottom_type(), StrongDependency, has_range_check());
return new CastIINode(in(0), in(1), bottom_type(), _dependency.with_pinned_dependency(), has_range_check());
}
return nullptr;
}
@ -315,16 +314,6 @@ void CastIINode::remove_range_check_cast(Compile* C) {
}
const Type* CastLLNode::Value(PhaseGVN* phase) const {
const Type* res = ConstraintCastNode::Value(phase);
if (res == Type::TOP) {
return Type::TOP;
}
assert(res->isa_long(), "res must be long");
return widen_type(phase, res, T_LONG);
}
bool CastLLNode::is_inner_loop_backedge(ProjNode* proj) {
if (proj != nullptr) {
Node* ctrl_use = proj->unique_ctrl_out_or_null();
@ -392,7 +381,7 @@ Node* CastLLNode::Ideal(PhaseGVN* phase, bool can_reshape) {
return progress;
}
if (!phase->C->post_loop_opts_phase()) {
// makes sure we run ::Value to potentially remove type assertion after loop opts
// makes sure we run widen_type() to potentially common type assertions after loop opts
phase->C->record_for_post_loop_opts_igvn(this);
}
// transform (CastLL (ConvI2L ..)) into (ConvI2L (CastII ..)) if the type of the CastLL is narrower than the type of
@ -543,7 +532,7 @@ Node* CastP2XNode::Identity(PhaseGVN* phase) {
return this;
}
Node* ConstraintCastNode::make_cast_for_type(Node* c, Node* in, const Type* type, DependencyType dependency,
Node* ConstraintCastNode::make_cast_for_type(Node* c, Node* in, const Type* type, const DependencyType& dependency,
const TypeTuple* types) {
if (type->isa_int()) {
return new CastIINode(c, in, type, dependency, false, types);
@ -564,7 +553,7 @@ Node* ConstraintCastNode::make_cast_for_type(Node* c, Node* in, const Type* type
return nullptr;
}
Node* ConstraintCastNode::optimize_integer_cast(PhaseGVN* phase, BasicType bt) {
Node* ConstraintCastNode::optimize_integer_cast_of_add(PhaseGVN* phase, BasicType bt) {
PhaseIterGVN *igvn = phase->is_IterGVN();
const TypeInteger* this_type = this->type()->isa_integer(bt);
if (this_type == nullptr) {
@ -586,8 +575,42 @@ Node* ConstraintCastNode::optimize_integer_cast(PhaseGVN* phase, BasicType bt) {
Node* x = z->in(1);
Node* y = z->in(2);
Node* cx = find_or_make_integer_cast(igvn, x, rx);
Node* cy = find_or_make_integer_cast(igvn, y, ry);
const TypeInteger* tx = phase->type(x)->is_integer(bt);
const TypeInteger* ty = phase->type(y)->is_integer(bt);
// (Cast (Add x y) tz) is transformed into (Add (Cast x rx) (Cast y ry))
//
// tz = [tzlo, tzhi]
// rx = [rxlo, rxhi]
// ry = [rylo, ryhi]
// with type of x, tx = [txlo, txhi]
// with type of y, ty = [tylo, tyhi]
//
// From Compile::push_thru_add():
// rxlo = max(tzlo - tyhi, txlo)
// rxhi = min(tzhi - tylo, txhi)
// rylo = max(tzlo - txhi, tylo)
// ryhi = min(tzhi - txlo, tyhi)
//
// If x is a constant, then txlo = txhi
// rxlo = txlo, rxhi = txhi
// The bounds of the type of the Add after transformation then is:
// rxlo + rylo >= txlo + tzlo - txhi >= tzlo
// rxhi + ryhi <= txhi + tzhi - txlo <= tzhi
// The resulting type is not wider than the type of the Cast
// before transformation
//
// If neither x nor y are constant then the type of the resulting
// Add can be wider than the type of the type of the Cast before
// transformation.
// For instance, tx = [0, 10], ty = [0, 10], tz = [0, 10]
// then rx = [0, 10], ry = [0, 10]
// and rx + ry = [0, 20] which is wider than tz
//
// Same reasoning applies to (Cast (Sub x y) tz)
const DependencyType& dependency = (!tx->is_con() && !ty->is_con()) ? _dependency.with_non_narrowing() : _dependency;
Node* cx = find_or_make_integer_cast(igvn, x, rx, dependency);
Node* cy = find_or_make_integer_cast(igvn, y, ry, dependency);
if (op == Op_Add(bt)) {
return AddNode::make(cx, cy, bt);
} else {
@ -599,11 +622,26 @@ Node* ConstraintCastNode::optimize_integer_cast(PhaseGVN* phase, BasicType bt) {
return nullptr;
}
const Type* ConstraintCastNode::widen_type(const PhaseGVN* phase, const Type* res, BasicType bt) const {
if (!phase->C->post_loop_opts_phase()) {
Node* ConstraintCastNode::optimize_integer_cast(PhaseGVN* phase, BasicType bt) {
Node* res = optimize_integer_cast_of_add(phase, bt);
if (res != nullptr) {
return res;
}
const Type* t = Value(phase);
if (t != Type::TOP && phase->C->post_loop_opts_phase()) {
const Type* bottom_t = bottom_type();
const TypeInteger* wide_t = widen_type(phase, bottom_t, bt);
if (wide_t != bottom_t) {
// Widening the type of the Cast (to allow some commoning) causes the Cast to change how it can be optimized (if
// type of its input is narrower than the Cast's type, we can't remove it to not loose the control dependency).
return make_with(in(1), wide_t, _dependency.with_non_narrowing());
}
}
return nullptr;
}
const TypeInteger* ConstraintCastNode::widen_type(const PhaseGVN* phase, const Type* res, BasicType bt) const {
const TypeInteger* this_type = res->is_integer(bt);
// At VerifyConstraintCasts == 1, we verify the ConstraintCastNodes that are present during code
// emission. This allows us detecting possible mis-scheduling due to these nodes being pinned at
// the wrong control nodes.
@ -612,10 +650,9 @@ const Type* ConstraintCastNode::widen_type(const PhaseGVN* phase, const Type* re
// mis-transformations that may happen due to these nodes being pinned at the wrong control
// nodes.
if (VerifyConstraintCasts > 1) {
return res;
return this_type;
}
const TypeInteger* this_type = res->is_integer(bt);
const TypeInteger* in_type = phase->type(in(1))->isa_integer(bt);
if (in_type != nullptr &&
(in_type->lo_as_long() != this_type->lo_as_long() ||
@ -636,5 +673,5 @@ const Type* ConstraintCastNode::widen_type(const PhaseGVN* phase, const Type* re
MIN2(in_type->hi_as_long(), hi1),
MAX2((int)in_type->_widen, w1), bt);
}
return res;
return this_type;
}

View File

@ -33,21 +33,119 @@
// cast to a different range
class ConstraintCastNode: public TypeNode {
public:
enum DependencyType {
RegularDependency, // if cast doesn't improve input type, cast can be removed
StrongDependency, // leave cast in even if _type doesn't improve input type, can be replaced by stricter dominating cast if one exist
UnconditionalDependency // leave cast in unconditionally
// Cast nodes are subject to a few optimizations:
//
// 1- if the type carried by the Cast doesn't narrow the type of its input, the cast can be replaced by its input.
// Similarly, if a dominating Cast with the same input and a narrower type constraint is found, it can replace the
// current cast.
//
// 2- if the condition that the Cast is control dependent is hoisted, the Cast is hoisted as well
//
// 1- and 2- are not always applied depending on what constraint are applied to the Cast: there are cases where 1-
// and 2- apply, where neither 1- nor 2- apply and where one or the other apply. This class abstract away these
// details.
//
// If _narrows_type is true, the cast carries a type dependency: "after" the control the cast is dependent on, its data
// input is known to have a narrower type (stored in the cast node itself). Optimizations 1- above only apply to cast
// nodes for which _narrows_type is true.
// if _floating is true, the cast only depends on a single control: its control input. Otherwise, it is pinned at its
// current location. Optimizations 2- only apply to cast nodes for which _floating is true.
// _floating here is similar to Node::depends_only_on_test().
// The 4 combinations of _narrows_types/_floating true/false have some use. See below, at the end of this class
// definition, for examples.
class DependencyType {
private:
const bool _floating; // Does this Cast depends on its control input or is it pinned?
const bool _narrows_type; // Does this Cast narrows the type i.e. if input type is narrower can it be removed?
const char* _desc;
DependencyType(bool depends_on_test, bool narrows_type, const char* desc)
: _floating(depends_on_test),
_narrows_type(narrows_type),
_desc(desc) {
}
NONCOPYABLE(DependencyType);
public:
bool is_floating() const {
return _floating;
}
bool narrows_type() const {
return _narrows_type;
}
void dump_on(outputStream *st) const {
st->print("%s", _desc);
}
uint hash() const {
return (_floating ? 1 : 0) + (_narrows_type ? 2 : 0);
}
bool cmp(const DependencyType& other) const {
return _floating == other._floating && _narrows_type == other._narrows_type;
}
const DependencyType& with_non_narrowing() const {
if (_floating) {
return FloatingNonNarrowing;
}
return NonFloatingNonNarrowing;
}
const DependencyType& with_pinned_dependency() const {
if (_narrows_type) {
return NonFloatingNarrowing;
}
return NonFloatingNonNarrowing;
}
// All the possible combinations of floating/narrowing with example use cases:
// Use case example: Range Check CastII
// Floating: The Cast is only dependent on the single range check. If the range check was ever to be hoisted it
// would be safe to let the Cast float to where the range check is hoisted up to.
// Narrowing: The Cast narrows the type to a positive index. If the input to the Cast is narrower, we can safely
// remove the cast because the array access will be safe.
static const DependencyType FloatingNarrowing;
// Use case example: Widening Cast nodes' types after loop opts: We want to common Casts with slightly different types.
// Floating: These Casts only depend on the single control.
// NonNarrowing: Even when the input type is narrower, we are not removing the Cast. Otherwise, the dependency
// to the single control is lost, and an array access could float above its range check because we
// just removed the dependency to the range check by removing the Cast. This could lead to an
// out-of-bounds access.
static const DependencyType FloatingNonNarrowing;
// Use case example: An array accesses that is no longer dependent on a single range check (e.g. range check smearing).
// NonFloating: The array access must be pinned below all the checks it depends on. If the check it directly depends
// on with a control input is hoisted, we do not hoist the Cast as well. If we allowed the Cast to float,
// we risk that the array access ends up above another check it depends on (we cannot model two control
// dependencies for a node in the IR). This could lead to an out-of-bounds access.
// Narrowing: If the Cast does not narrow the input type, then it's safe to remove the cast because the array access
// will be safe.
static const DependencyType NonFloatingNarrowing;
// Use case example: Sinking nodes out of a loop
// Non-Floating & Non-Narrowing: We don't want the Cast that forces the node to be out of loop to be removed in any
// case. Otherwise, the sunk node could float back into the loop, undoing the sinking.
// This Cast is only used for pinning without caring about narrowing types.
static const DependencyType NonFloatingNonNarrowing;
};
protected:
const DependencyType _dependency;
protected:
const DependencyType& _dependency;
virtual bool cmp( const Node &n ) const;
virtual uint size_of() const;
virtual uint hash() const; // Check the type
const Type* widen_type(const PhaseGVN* phase, const Type* res, BasicType bt) const;
Node* find_or_make_integer_cast(PhaseIterGVN* igvn, Node* parent, const TypeInteger* type) const;
const TypeInteger* widen_type(const PhaseGVN* phase, const Type* res, BasicType bt) const;
virtual ConstraintCastNode* make_with(Node* parent, const TypeInteger* type, const DependencyType& dependency) const {
ShouldNotReachHere(); // Only implemented for CastII and CastLL
return nullptr;
}
Node* find_or_make_integer_cast(PhaseIterGVN* igvn, Node* parent, const TypeInteger* type, const DependencyType& dependency) const;
private:
// PhiNode::Ideal() transforms a Phi that merges a single uncasted value into a single cast pinned at the region.
// The types of cast nodes eliminated as a consequence of this transformation are collected and stored here so the
// type dependencies carried by the cast are known. The cast can then be eliminated if the type of its input is
@ -55,7 +153,7 @@ public:
const TypeTuple* _extra_types;
public:
ConstraintCastNode(Node* ctrl, Node* n, const Type* t, ConstraintCastNode::DependencyType dependency,
ConstraintCastNode(Node* ctrl, Node* n, const Type* t, const DependencyType& dependency,
const TypeTuple* extra_types)
: TypeNode(t,2), _dependency(dependency), _extra_types(extra_types) {
init_class_id(Class_ConstraintCast);
@ -67,18 +165,21 @@ public:
virtual Node *Ideal(PhaseGVN *phase, bool can_reshape);
virtual int Opcode() const;
virtual uint ideal_reg() const = 0;
virtual bool depends_only_on_test() const { return _dependency == RegularDependency; }
bool carry_dependency() const { return _dependency != RegularDependency; }
bool carry_dependency() const { return !_dependency.cmp(DependencyType::FloatingNarrowing); }
// A cast node depends_only_on_test if and only if it is floating
virtual bool depends_only_on_test() const { return _dependency.is_floating(); }
const DependencyType& dependency() const { return _dependency; }
TypeNode* dominating_cast(PhaseGVN* gvn, PhaseTransform* pt) const;
static Node* make_cast_for_basic_type(Node* c, Node* n, const Type* t, DependencyType dependency, BasicType bt);
static Node* make_cast_for_basic_type(Node* c, Node* n, const Type* t, const DependencyType& dependency, BasicType bt);
#ifndef PRODUCT
virtual void dump_spec(outputStream *st) const;
#endif
static Node* make_cast_for_type(Node* c, Node* in, const Type* type, DependencyType dependency,
static Node* make_cast_for_type(Node* c, Node* in, const Type* type, const DependencyType& dependency,
const TypeTuple* types);
Node* optimize_integer_cast_of_add(PhaseGVN* phase, BasicType bt);
Node* optimize_integer_cast(PhaseGVN* phase, BasicType bt);
bool higher_equal_types(PhaseGVN* phase, const Node* other) const;
@ -102,7 +203,7 @@ class CastIINode: public ConstraintCastNode {
virtual uint size_of() const;
public:
CastIINode(Node* ctrl, Node* n, const Type* t, DependencyType dependency = RegularDependency, bool range_check_dependency = false, const TypeTuple* types = nullptr)
CastIINode(Node* ctrl, Node* n, const Type* t, const DependencyType& dependency = DependencyType::FloatingNarrowing, bool range_check_dependency = false, const TypeTuple* types = nullptr)
: ConstraintCastNode(ctrl, n, t, dependency, types), _range_check_dependency(range_check_dependency) {
assert(ctrl != nullptr, "control must be set");
init_class_id(Class_CastII);
@ -110,7 +211,7 @@ class CastIINode: public ConstraintCastNode {
virtual int Opcode() const;
virtual uint ideal_reg() const { return Op_RegI; }
virtual Node* Identity(PhaseGVN* phase);
virtual const Type* Value(PhaseGVN* phase) const;
virtual Node *Ideal(PhaseGVN *phase, bool can_reshape);
bool has_range_check() const {
#ifdef _LP64
@ -122,6 +223,7 @@ class CastIINode: public ConstraintCastNode {
}
CastIINode* pin_array_access_node() const;
CastIINode* make_with(Node* parent, const TypeInteger* type, const DependencyType& dependency) const;
void remove_range_check_cast(Compile* C);
#ifndef PRODUCT
@ -131,14 +233,12 @@ class CastIINode: public ConstraintCastNode {
class CastLLNode: public ConstraintCastNode {
public:
CastLLNode(Node* ctrl, Node* n, const Type* t, DependencyType dependency = RegularDependency, const TypeTuple* types = nullptr)
CastLLNode(Node* ctrl, Node* n, const Type* t, const DependencyType& dependency = DependencyType::FloatingNarrowing, const TypeTuple* types = nullptr)
: ConstraintCastNode(ctrl, n, t, dependency, types) {
assert(ctrl != nullptr, "control must be set");
init_class_id(Class_CastLL);
}
virtual const Type* Value(PhaseGVN* phase) const;
static bool is_inner_loop_backedge(ProjNode* proj);
static bool cmp_used_at_inner_loop_exit_test(CmpNode* cmp);
@ -147,11 +247,12 @@ public:
virtual Node* Ideal(PhaseGVN* phase, bool can_reshape);
virtual int Opcode() const;
virtual uint ideal_reg() const { return Op_RegL; }
CastLLNode* make_with(Node* parent, const TypeInteger* type, const DependencyType& dependency) const;
};
class CastHHNode: public ConstraintCastNode {
public:
CastHHNode(Node* ctrl, Node* n, const Type* t, DependencyType dependency = RegularDependency, const TypeTuple* types = nullptr)
CastHHNode(Node* ctrl, Node* n, const Type* t, const DependencyType& dependency = DependencyType::FloatingNarrowing, const TypeTuple* types = nullptr)
: ConstraintCastNode(ctrl, n, t, dependency, types) {
assert(ctrl != nullptr, "control must be set");
init_class_id(Class_CastHH);
@ -162,7 +263,7 @@ public:
class CastFFNode: public ConstraintCastNode {
public:
CastFFNode(Node* ctrl, Node* n, const Type* t, DependencyType dependency = RegularDependency, const TypeTuple* types = nullptr)
CastFFNode(Node* ctrl, Node* n, const Type* t, const DependencyType& dependency = DependencyType::FloatingNarrowing, const TypeTuple* types = nullptr)
: ConstraintCastNode(ctrl, n, t, dependency, types) {
assert(ctrl != nullptr, "control must be set");
init_class_id(Class_CastFF);
@ -173,7 +274,7 @@ public:
class CastDDNode: public ConstraintCastNode {
public:
CastDDNode(Node* ctrl, Node* n, const Type* t, DependencyType dependency = RegularDependency, const TypeTuple* types = nullptr)
CastDDNode(Node* ctrl, Node* n, const Type* t, const DependencyType& dependency = DependencyType::FloatingNarrowing, const TypeTuple* types = nullptr)
: ConstraintCastNode(ctrl, n, t, dependency, types) {
assert(ctrl != nullptr, "control must be set");
init_class_id(Class_CastDD);
@ -184,7 +285,7 @@ public:
class CastVVNode: public ConstraintCastNode {
public:
CastVVNode(Node* ctrl, Node* n, const Type* t, DependencyType dependency = RegularDependency, const TypeTuple* types = nullptr)
CastVVNode(Node* ctrl, Node* n, const Type* t, const DependencyType& dependency = DependencyType::FloatingNarrowing, const TypeTuple* types = nullptr)
: ConstraintCastNode(ctrl, n, t, dependency, types) {
assert(ctrl != nullptr, "control must be set");
init_class_id(Class_CastVV);
@ -198,7 +299,7 @@ public:
// cast pointer to pointer (different type)
class CastPPNode: public ConstraintCastNode {
public:
CastPPNode (Node* ctrl, Node* n, const Type* t, DependencyType dependency = RegularDependency, const TypeTuple* types = nullptr)
CastPPNode (Node* ctrl, Node* n, const Type* t, const DependencyType& dependency = DependencyType::FloatingNarrowing, const TypeTuple* types = nullptr)
: ConstraintCastNode(ctrl, n, t, dependency, types) {
init_class_id(Class_CastPP);
}
@ -210,7 +311,7 @@ class CastPPNode: public ConstraintCastNode {
// for _checkcast, cast pointer to pointer (different type), without JOIN,
class CheckCastPPNode: public ConstraintCastNode {
public:
CheckCastPPNode(Node* ctrl, Node* n, const Type* t, DependencyType dependency = RegularDependency, const TypeTuple* types = nullptr)
CheckCastPPNode(Node* ctrl, Node* n, const Type* t, const DependencyType& dependency = DependencyType::FloatingNarrowing, const TypeTuple* types = nullptr)
: ConstraintCastNode(ctrl, n, t, dependency, types) {
assert(ctrl != nullptr, "control must be set");
init_class_id(Class_CheckCastPP);

View File

@ -2192,7 +2192,7 @@ Node *PhiNode::Ideal(PhaseGVN *phase, bool can_reshape) {
if (phi_type->isa_ptr()) {
const Type* uin_type = phase->type(uin);
if (!phi_type->isa_oopptr() && !uin_type->isa_oopptr()) {
cast = new CastPPNode(r, uin, phi_type, ConstraintCastNode::StrongDependency, extra_types);
cast = new CastPPNode(r, uin, phi_type, ConstraintCastNode::DependencyType::NonFloatingNarrowing, extra_types);
} else {
// Use a CastPP for a cast to not null and a CheckCastPP for
// a cast to a new klass (and both if both null-ness and
@ -2202,7 +2202,7 @@ Node *PhiNode::Ideal(PhaseGVN *phase, bool can_reshape) {
// null, uin's type must be casted to not null
if (phi_type->join(TypePtr::NOTNULL) == phi_type->remove_speculative() &&
uin_type->join(TypePtr::NOTNULL) != uin_type->remove_speculative()) {
cast = new CastPPNode(r, uin, TypePtr::NOTNULL, ConstraintCastNode::StrongDependency, extra_types);
cast = new CastPPNode(r, uin, TypePtr::NOTNULL, ConstraintCastNode::DependencyType::NonFloatingNarrowing, extra_types);
}
// If the type of phi and uin, both casted to not null,
@ -2214,14 +2214,14 @@ Node *PhiNode::Ideal(PhaseGVN *phase, bool can_reshape) {
cast = phase->transform(cast);
n = cast;
}
cast = new CheckCastPPNode(r, n, phi_type, ConstraintCastNode::StrongDependency, extra_types);
cast = new CheckCastPPNode(r, n, phi_type, ConstraintCastNode::DependencyType::NonFloatingNarrowing, extra_types);
}
if (cast == nullptr) {
cast = new CastPPNode(r, uin, phi_type, ConstraintCastNode::StrongDependency, extra_types);
cast = new CastPPNode(r, uin, phi_type, ConstraintCastNode::DependencyType::NonFloatingNarrowing, extra_types);
}
}
} else {
cast = ConstraintCastNode::make_cast_for_type(r, uin, phi_type, ConstraintCastNode::StrongDependency, extra_types);
cast = ConstraintCastNode::make_cast_for_type(r, uin, phi_type, ConstraintCastNode::DependencyType::NonFloatingNarrowing, extra_types);
}
assert(cast != nullptr, "cast should be set");
cast = phase->transform(cast);

View File

@ -4578,7 +4578,7 @@ Node* Compile::constrained_convI2L(PhaseGVN* phase, Node* value, const TypeInt*
// node from floating above the range check during loop optimizations. Otherwise, the
// ConvI2L node may be eliminated independently of the range check, causing the data path
// to become TOP while the control path is still there (although it's unreachable).
value = new CastIINode(ctrl, value, itype, carry_dependency ? ConstraintCastNode::StrongDependency : ConstraintCastNode::RegularDependency, true /* range check dependency */);
value = new CastIINode(ctrl, value, itype, carry_dependency ? ConstraintCastNode::DependencyType::NonFloatingNarrowing : ConstraintCastNode::DependencyType::FloatingNarrowing, true /* range check dependency */);
value = phase->transform(value);
}
const TypeLong* ltype = TypeLong::make(itype->_lo, itype->_hi, itype->_widen);

View File

@ -748,7 +748,7 @@ Node* ConnectionGraph::specialize_castpp(Node* castpp, Node* base, Node* current
_igvn->_worklist.push(current_control);
_igvn->_worklist.push(control_successor);
return _igvn->transform(ConstraintCastNode::make_cast_for_type(not_eq_control, base, _igvn->type(castpp), ConstraintCastNode::UnconditionalDependency, nullptr));
return _igvn->transform(ConstraintCastNode::make_cast_for_type(not_eq_control, base, _igvn->type(castpp), ConstraintCastNode::DependencyType::NonFloatingNonNarrowing, nullptr));
}
Node* ConnectionGraph::split_castpp_load_through_phi(Node* curr_addp, Node* curr_load, Node* region, GrowableArray<Node*>* bases_for_loads, GrowableArray<Node *> &alloc_worklist) {
@ -1235,7 +1235,7 @@ bool ConnectionGraph::reduce_phi_on_safepoints_helper(Node* ophi, Node* cast, No
Node* nsr_merge_pointer = ophi;
if (cast != nullptr) {
const Type* new_t = merge_t->meet(TypePtr::NULL_PTR);
nsr_merge_pointer = _igvn->transform(ConstraintCastNode::make_cast_for_type(cast->in(0), cast->in(1), new_t, ConstraintCastNode::RegularDependency, nullptr));
nsr_merge_pointer = _igvn->transform(ConstraintCastNode::make_cast_for_type(cast->in(0), cast->in(1), new_t, ConstraintCastNode::DependencyType::FloatingNarrowing, nullptr));
}
for (uint spi = 0; spi < safepoints.size(); spi++) {
@ -1376,7 +1376,7 @@ void ConnectionGraph::reset_scalar_replaceable_entries(PhiNode* ophi) {
}
if (change) {
Node* new_cast = ConstraintCastNode::make_cast_for_type(out->in(0), out->in(1), out_new_t, ConstraintCastNode::StrongDependency, nullptr);
Node* new_cast = ConstraintCastNode::make_cast_for_type(out->in(0), out->in(1), out_new_t, ConstraintCastNode::DependencyType::NonFloatingNarrowing, nullptr);
_igvn->replace_node(out, new_cast);
_igvn->register_new_node_with_optimizer(new_cast);
}

View File

@ -1183,7 +1183,7 @@ bool LibraryCallKit::inline_preconditions_checkIndex(BasicType bt) {
jlong upper_bound = _gvn.type(length)->is_integer(bt)->hi_as_long();
Node* casted_length = ConstraintCastNode::make_cast_for_basic_type(
control(), length, TypeInteger::make(0, upper_bound, Type::WidenMax, bt),
ConstraintCastNode::RegularDependency, bt);
ConstraintCastNode::DependencyType::FloatingNarrowing, bt);
casted_length = _gvn.transform(casted_length);
replace_in_map(length, casted_length);
length = casted_length;
@ -1213,7 +1213,7 @@ bool LibraryCallKit::inline_preconditions_checkIndex(BasicType bt) {
// index is now known to be >= 0 and < length, cast it
Node* result = ConstraintCastNode::make_cast_for_basic_type(
control(), index, TypeInteger::make(0, upper_bound, Type::WidenMax, bt),
ConstraintCastNode::RegularDependency, bt);
ConstraintCastNode::DependencyType::FloatingNarrowing, bt);
result = _gvn.transform(result);
set_result(result);
replace_in_map(index, result);

View File

@ -1366,7 +1366,7 @@ Node *PhaseIdealLoop::clone_up_backedge_goo(Node *back_ctrl, Node *preheader_ctr
// the backedge of the main or post loop is removed, a Div node won't be able to float above the zero trip guard of the
// loop and can't execute even if the loop is not reached.
void PhaseIdealLoop::cast_incr_before_loop(Node* incr, Node* ctrl, CountedLoopNode* loop) {
Node* castii = new CastIINode(ctrl, incr, TypeInt::INT, ConstraintCastNode::UnconditionalDependency);
Node* castii = new CastIINode(ctrl, incr, TypeInt::INT, ConstraintCastNode::DependencyType::NonFloatingNonNarrowing);
register_new_node(castii, ctrl);
Node* phi = loop->phi();
assert(phi->in(LoopNode::EntryControl) == incr, "replacing wrong input?");
@ -3262,7 +3262,7 @@ bool IdealLoopTree::do_remove_empty_loop(PhaseIdealLoop *phase) {
Node* cast_ii = ConstraintCastNode::make_cast_for_basic_type(
cl->in(LoopNode::EntryControl), exact_limit,
phase->_igvn.type(exact_limit),
ConstraintCastNode::UnconditionalDependency, T_INT);
ConstraintCastNode::DependencyType::NonFloatingNonNarrowing, T_INT);
phase->register_new_node(cast_ii, cl->in(LoopNode::EntryControl));
Node* final_iv = new SubINode(cast_ii, cl->stride());

View File

@ -1001,7 +1001,7 @@ bool PhaseIdealLoop::create_loop_nest(IdealLoopTree* loop, Node_List &old_new) {
// a negative stride). We add a CastII here to guarantee that, when the counted loop is created in a subsequent loop
// opts pass, an accurate range of values for the limits is found.
const TypeInt* inner_iters_actual_int_range = TypeInt::make(0, iters_limit, Type::WidenMin);
inner_iters_actual_int = new CastIINode(outer_head, inner_iters_actual_int, inner_iters_actual_int_range, ConstraintCastNode::UnconditionalDependency);
inner_iters_actual_int = new CastIINode(outer_head, inner_iters_actual_int, inner_iters_actual_int_range, ConstraintCastNode::DependencyType::NonFloatingNonNarrowing);
_igvn.register_new_node_with_optimizer(inner_iters_actual_int);
} else {
inner_iters_actual_int = inner_iters_actual;
@ -1315,7 +1315,7 @@ bool PhaseIdealLoop::try_make_short_running_loop(IdealLoopTree* loop, jint strid
register_new_node(bol, iff->in(0));
new_limit = ConstraintCastNode::make_cast_for_basic_type(new_predicate_proj, new_limit,
TypeInteger::make(1, iters_limit_long, Type::WidenMin, bt),
ConstraintCastNode::UnconditionalDependency, bt);
ConstraintCastNode::DependencyType::NonFloatingNonNarrowing, bt);
register_new_node(new_limit, new_predicate_proj);
#ifndef PRODUCT
@ -1334,7 +1334,7 @@ bool PhaseIdealLoop::try_make_short_running_loop(IdealLoopTree* loop, jint strid
const TypeLong* new_limit_t = new_limit->Value(&_igvn)->is_long();
new_limit = ConstraintCastNode::make_cast_for_basic_type(predicates.entry(), new_limit,
TypeLong::make(0, new_limit_t->_hi, new_limit_t->_widen),
ConstraintCastNode::UnconditionalDependency, bt);
ConstraintCastNode::DependencyType::NonFloatingNonNarrowing, bt);
register_new_node(new_limit, predicates.entry());
} else {
assert(bt == T_INT && known_short_running_loop, "only CountedLoop statically known to be short running");

View File

@ -1174,7 +1174,7 @@ Node *PhaseIdealLoop::split_if_with_blocks_pre( Node *n ) {
if ( nn ) return nn;
}
if (n->is_ConstraintCast()) {
if (n->is_ConstraintCast() && n->as_ConstraintCast()->dependency().narrows_type()) {
Node* dom_cast = n->as_ConstraintCast()->dominating_cast(&_igvn, this);
// ConstraintCastNode::dominating_cast() uses node control input to determine domination.
// Node control inputs don't necessarily agree with loop control info (due to
@ -1837,7 +1837,7 @@ void PhaseIdealLoop::try_sink_out_of_loop(Node* n) {
if (in != nullptr && ctrl_is_member(n_loop, in)) {
const Type* in_t = _igvn.type(in);
cast = ConstraintCastNode::make_cast_for_type(x_ctrl, in, in_t,
ConstraintCastNode::UnconditionalDependency, nullptr);
ConstraintCastNode::DependencyType::NonFloatingNonNarrowing, nullptr);
}
if (cast != nullptr) {
Node* prev = _igvn.hash_find_insert(cast);

View File

@ -233,7 +233,7 @@ void PhaseMacroExpand::generate_partial_inlining_block(Node** ctrl, MergeMemNode
Node* inline_block = generate_guard(ctrl, bol_le, nullptr, PROB_FAIR);
Node* stub_block = *ctrl;
Node* casted_length = new CastLLNode(inline_block, length, inline_range, ConstraintCastNode::RegularDependency);
Node* casted_length = new CastLLNode(inline_block, length, inline_range, ConstraintCastNode::DependencyType::FloatingNarrowing);
transform_later(casted_length);
Node* mask_gen = VectorMaskGenNode::make(casted_length, type);
transform_later(mask_gen);

View File

@ -44,13 +44,13 @@ public class TestPushAddThruCast {
TestFramework.run();
}
final static int length = RANDOM.nextInt(Integer.MAX_VALUE);
final static long llength = RANDOM.nextInt(Integer.MAX_VALUE);
final static int length = RANDOM.nextInt(5, Integer.MAX_VALUE);
final static long llength = RANDOM.nextInt(2, Integer.MAX_VALUE);
static int i;
static long l;
@Test
@IR(counts = { IRNode.CAST_II, "1" })
@IR(counts = { IRNode.CAST_II, "2" })
public static int test1() {
int j = Objects.checkIndex(i, length);
int k = Objects.checkIndex(i + 1, length);
@ -67,7 +67,7 @@ public class TestPushAddThruCast {
}
@Test
@IR(counts = { IRNode.CAST_LL, "1" })
@IR(counts = { IRNode.CAST_LL, "2" })
public static long test2() {
long j = Objects.checkIndex(l, llength);
long k = Objects.checkIndex(l + 1, llength);
@ -82,4 +82,25 @@ public class TestPushAddThruCast {
throw new RuntimeException("incorrect result: " + res);
}
}
// Test commoning of Casts after loop opts when they are at the same control
@Test
@IR(phase = CompilePhase.ITER_GVN1, counts = { IRNode.CAST_II, "4" })
@IR(phase = CompilePhase.OPTIMIZE_FINISHED, counts = { IRNode.CAST_II, "2" })
public static int test3() {
int j = Objects.checkIndex(i - 3, length);
j += Objects.checkIndex(i, length);
j += Objects.checkIndex(i - 2, length);
j += Objects.checkIndex(i - 1, length);
return j;
}
@Run(test = "test3")
public static void test3_runner() {
i = RANDOM.nextInt(3, length - 1);
int res = test3();
if (res != i * 4 - 6) {
throw new RuntimeException("incorrect result: " + res + " for i = " + i);
}
}
}

View File

@ -44,7 +44,6 @@ public class TestArrayAccessAboveRCAfterRCCastIIEliminated {
private static volatile int volatileField;
public static void main(String[] args) {
int[] array = new int[100];
for (int i = 0; i < 20_000; i++) {
test1(9, 10, 1, true);
test1(9, 10, 1, false);
@ -72,6 +71,13 @@ public class TestArrayAccessAboveRCAfterRCCastIIEliminated {
test12(9, 10, 1, false);
test13(9, 10, 1, true);
test13(9, 10, 1, false);
test14(8, 0, 1, true);
test14(8, 0, 1, false);
inlined14(0, 0);
test15(8, 0, 1, true);
test15(8, 0, 1, false);
inlined15(0, 0);
}
try {
test1(-1, 10, 1, true);
@ -125,6 +131,14 @@ public class TestArrayAccessAboveRCAfterRCCastIIEliminated {
test13(-1, 10, 1, true);
} catch (ArrayIndexOutOfBoundsException arrayIndexOutOfBoundsException) {
}
try {
test14(Integer.MAX_VALUE, 10, 1, true);
} catch (ArrayIndexOutOfBoundsException arrayIndexOutOfBoundsException) {
}
try {
test15(Integer.MAX_VALUE, 10, 1, true);
} catch (ArrayIndexOutOfBoundsException arrayIndexOutOfBoundsException) {
}
}
private static void test1(int i, int j, int flag, boolean flag2) {
@ -468,6 +482,72 @@ public class TestArrayAccessAboveRCAfterRCCastIIEliminated {
}
}
// 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++);
j = inlined14(j, l);
int[] array = new int[10];
notInlined(array);
if (flag == 0) {
}
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) {
}
intField = array[otherArray.length];
} else {
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) {
}
intField = array[otherArray.length];
}
}
private static int inlined14(int j, int l) {
if (l == 10) {
j = 1;
}
return j;
}
private static void test15(int i, int j, int flag, boolean flag2) {
i = Integer.max(i, Integer.MIN_VALUE + 1);
int l = 0;
for (; l < 10; l++);
j = inlined15(j, l);
int[] array = new int[10];
notInlined(array);
if (flag == 0) {
}
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) {
}
intField = array[otherArray.length];
} else {
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) {
}
intField = array[otherArray.length];
}
}
private static int inlined15(int j, int l) {
if (l == 10) {
j = Integer.max(j, Integer.MIN_VALUE + 10);
}
return j;
}
private static void notInlined(int[] array) {
}