Compare commits

..

3 Commits

Author SHA1 Message Date
Damon Fenacci
0ef73ef943
JDK-8374852: fix star layout
Co-authored-by: Christian Hagedorn <christian.hagedorn@oracle.com>
2026-01-27 17:22:22 +01:00
Damon Fenacci
b79738c365 JDK-8374852: fix macro expansion for OpaqueCheck 2026-01-27 17:01:26 +01:00
Damon Fenacci
b028198173 JDK-8374852: use only one opaque node 2026-01-27 16:47:40 +01:00
16 changed files with 54 additions and 85 deletions

View File

@ -281,8 +281,7 @@ macro(OpaqueLoopInit)
macro(OpaqueLoopStride)
macro(OpaqueMultiversioning)
macro(OpaqueZeroTripGuard)
macro(OpaqueNotNull)
macro(OpaqueGuard)
macro(OpaqueCheck)
macro(OpaqueInitializedAssertionPredicate)
macro(OpaqueTemplateAssertionPredicate)
macro(ProfileBoolean)

View File

@ -588,7 +588,7 @@ bool ConnectionGraph::can_reduce_check_users(Node* n, uint nesting) const {
// CmpP/N used by the If controlling the cast.
if (use->in(0)->is_IfTrue() || use->in(0)->is_IfFalse()) {
Node* iff = use->in(0)->in(0);
// We may have an OpaqueNotNull node between If and Bool nodes. But we could also have a sub class of IfNode,
// We may have an OpaqueCheck node between If and Bool nodes. But we could also have a sub class of IfNode,
// for example, an OuterStripMinedLoopEnd or a Parse Predicate. Bail out in all these cases.
bool can_reduce = (iff->Opcode() == Op_If) && iff->in(1)->is_Bool() && iff->in(1)->in(1)->is_Cmp();
if (can_reduce) {

View File

@ -1472,7 +1472,7 @@ Node* GraphKit::cast_not_null(Node* obj, bool do_replace_in_map) {
// In that case that data path will die and we need the control path
// to become dead as well to keep the graph consistent. So we have to
// add a check for null for which one branch can't be taken. It uses
// an OpaqueNotNull node that will cause the check to be removed after loop
// an OpaqueCheck node that will cause the check to be removed after loop
// opts so the test goes away and the compiled code doesn't execute a
// useless check.
Node* GraphKit::must_be_not_null(Node* value, bool do_replace_in_map) {
@ -1481,7 +1481,7 @@ Node* GraphKit::must_be_not_null(Node* value, bool do_replace_in_map) {
}
Node* chk = _gvn.transform(new CmpPNode(value, null()));
Node* tst = _gvn.transform(new BoolNode(chk, BoolTest::ne));
Node* opaq = _gvn.transform(new OpaqueNotNullNode(C, tst));
Node* opaq = _gvn.transform(new OpaqueCheckNode(C, tst, true));
IfNode* iff = new IfNode(control(), opaq, PROB_MAX, COUNT_UNKNOWN);
_gvn.set_type(iff, iff->Value(&_gvn));
if (!tst->is_Con()) {

View File

@ -899,7 +899,7 @@ inline Node* LibraryCallKit::generate_negative_guard(Node* index, RegionNode* re
Node* cmp_lt = _gvn.transform(new CmpINode(index, intcon(0)));
Node* bol_lt = _gvn.transform(new BoolNode(cmp_lt, BoolTest::lt));
if (is_opaque) {
bol_lt = _gvn.transform(new OpaqueGuardNode(C, bol_lt));
bol_lt = _gvn.transform(new OpaqueCheckNode(C, bol_lt, false));
}
Node* is_neg = generate_guard(bol_lt, region, PROB_MIN);
if (is_neg != nullptr && pos_index != nullptr) {
@ -940,7 +940,7 @@ inline Node* LibraryCallKit::generate_limit_guard(Node* offset,
Node* cmp_lt = _gvn.transform(new CmpUNode(array_length, last));
Node* bol_lt = _gvn.transform(new BoolNode(cmp_lt, BoolTest::lt));
if (is_opaque) {
bol_lt = _gvn.transform(new OpaqueGuardNode(C, bol_lt));
bol_lt = _gvn.transform(new OpaqueCheckNode(C, bol_lt, false));
}
Node* is_over = generate_guard(bol_lt, region, PROB_MIN);
return is_over;

View File

@ -158,7 +158,7 @@ class LibraryCallKit : public GraphKit {
Node* generate_fair_guard(Node* test, RegionNode* region);
Node* generate_negative_guard(Node* index, RegionNode* region,
// resulting CastII of index:
Node* *pos_index = nullptr,
Node** pos_index = nullptr,
bool is_opaque = false);
Node* generate_limit_guard(Node* offset, Node* subseq_length,
Node* array_length,

View File

@ -1234,8 +1234,7 @@ bool IdealLoopTree::policy_range_check(PhaseIdealLoop* phase, bool provisional,
continue;
}
if (!bol->is_Bool()) {
assert(bol->is_OpaqueNotNull() ||
bol->is_OpaqueGuard() ||
assert(bol->is_OpaqueCheck() ||
bol->is_OpaqueTemplateAssertionPredicate() ||
bol->is_OpaqueInitializedAssertionPredicate() ||
bol->is_OpaqueMultiversioning(),

View File

@ -1704,8 +1704,7 @@ void PhaseIdealLoop::try_sink_out_of_loop(Node* n) {
!n->is_Proj() &&
!n->is_MergeMem() &&
!n->is_CMove() &&
!n->is_OpaqueNotNull() &&
!n->is_OpaqueGuard() &&
!n->is_OpaqueCheck() &&
!n->is_OpaqueInitializedAssertionPredicate() &&
!n->is_OpaqueTemplateAssertionPredicate() &&
!is_raw_to_oop_cast && // don't extend live ranges of raw oops
@ -2046,14 +2045,14 @@ Node* PhaseIdealLoop::clone_iff(PhiNode* phi) {
if (b->is_Phi()) {
_igvn.replace_input_of(phi, i, clone_iff(b->as_Phi()));
} else {
assert(b->is_Bool() || b->is_OpaqueNotNull() || b->is_OpaqueGuard() || b->is_OpaqueInitializedAssertionPredicate(),
"bool, non-null check with OpaqueNotNull or Initialized Assertion Predicate with its Opaque node");
assert(b->is_Bool() || b->is_OpaqueCheck() || b->is_OpaqueInitializedAssertionPredicate(),
"bool, non-null check with OpaqueCheck or Initialized Assertion Predicate with its Opaque node");
}
}
Node* n = phi->in(1);
Node* sample_opaque = nullptr;
Node *sample_bool = nullptr;
if (n->is_OpaqueNotNull() || n->is_OpaqueInitializedAssertionPredicate() || n->is_OpaqueGuard()) {
if (n->is_OpaqueCheck() || n->is_OpaqueInitializedAssertionPredicate()) {
sample_opaque = n;
sample_bool = n->in(1);
assert(sample_bool->is_Bool(), "wrong type");
@ -2229,7 +2228,7 @@ void PhaseIdealLoop::clone_loop_handle_data_uses(Node* old, Node_List &old_new,
// split if to break.
assert(!use->is_OpaqueTemplateAssertionPredicate(),
"should not clone a Template Assertion Predicate which should be removed once it's useless");
if (use->is_If() || use->is_CMove() || use->is_OpaqueGuard() || use->is_OpaqueNotNull() || use->is_OpaqueInitializedAssertionPredicate() ||
if (use->is_If() || use->is_CMove() || use->is_OpaqueCheck() || use->is_OpaqueInitializedAssertionPredicate() ||
(use->Opcode() == Op_AllocateArray && use->in(AllocateNode::ValidLengthTest) == old)) {
// Since this code is highly unlikely, we lazily build the worklist
// of such Nodes to go split.

View File

@ -2500,8 +2500,7 @@ void PhaseMacroExpand::eliminate_macro_nodes() {
assert(n->Opcode() == Op_LoopLimit ||
n->Opcode() == Op_ModD ||
n->Opcode() == Op_ModF ||
n->is_OpaqueNotNull() ||
n->is_OpaqueGuard() ||
n->is_OpaqueCheck() ||
n->is_OpaqueInitializedAssertionPredicate() ||
n->Opcode() == Op_MaxL ||
n->Opcode() == Op_MinL ||
@ -2549,24 +2548,15 @@ void PhaseMacroExpand::eliminate_opaque_looplimit_macro_nodes() {
} else if (n->is_Opaque1()) {
_igvn.replace_node(n, n->in(1));
success = true;
} else if (n->is_OpaqueNotNull()) {
// Tests with OpaqueNotNull nodes are implicitly known to be true. Replace the node with true. In debug builds,
} else if (n->is_OpaqueCheck()) {
// Tests with OpaqueCheck nodes are implicitly known. Replace the node with true/false. In debug builds,
// we leave the test in the graph to have an additional sanity check at runtime. If the test fails (i.e. a bug),
// we will execute a Halt node.
#ifdef ASSERT
_igvn.replace_node(n, n->in(1));
#else
_igvn.replace_node(n, _igvn.intcon(1));
#endif
success = true;
} else if (n->is_OpaqueGuard()) {
// Tests with OpaqueGuard nodes are implicitly known to be false. Replace the node with false. In debug
// builds, we leave the test in the graph to have an additional sanity check at runtime. If the test
// fails (i.e. a bug), we will execute a Halt node.
#ifdef ASSERT
_igvn.replace_node(n, n->in(1));
#else
_igvn.replace_node(n, _igvn.intcon(0));
bool is_positive = n->as_OpaqueCheck()->is_positive();
_igvn.replace_node(n, _igvn.intcon(is_positive?1:0));
#endif
success = true;
} else if (n->is_OpaqueInitializedAssertionPredicate()) {

View File

@ -142,8 +142,7 @@ class Opaque1Node;
class OpaqueLoopInitNode;
class OpaqueLoopStrideNode;
class OpaqueMultiversioningNode;
class OpaqueNotNullNode;
class OpaqueGuardNode;
class OpaqueCheckNode;
class OpaqueInitializedAssertionPredicateNode;
class OpaqueTemplateAssertionPredicateNode;
class OuterStripMinedLoopNode;
@ -817,13 +816,12 @@ public:
DEFINE_CLASS_ID(OpaqueLoopInit, Opaque1, 0)
DEFINE_CLASS_ID(OpaqueLoopStride, Opaque1, 1)
DEFINE_CLASS_ID(OpaqueMultiversioning, Opaque1, 2)
DEFINE_CLASS_ID(OpaqueNotNull, Node, 17)
DEFINE_CLASS_ID(OpaqueCheck, Node, 17)
DEFINE_CLASS_ID(OpaqueInitializedAssertionPredicate, Node, 18)
DEFINE_CLASS_ID(OpaqueTemplateAssertionPredicate, Node, 19)
DEFINE_CLASS_ID(Move, Node, 20)
DEFINE_CLASS_ID(LShift, Node, 21)
DEFINE_CLASS_ID(Neg, Node, 22)
DEFINE_CLASS_ID(OpaqueGuard, Node, 23)
_max_classes = ClassMask_Neg
};
@ -998,8 +996,7 @@ public:
DEFINE_CLASS_QUERY(NegV)
DEFINE_CLASS_QUERY(NeverBranch)
DEFINE_CLASS_QUERY(Opaque1)
DEFINE_CLASS_QUERY(OpaqueNotNull)
DEFINE_CLASS_QUERY(OpaqueGuard)
DEFINE_CLASS_QUERY(OpaqueCheck)
DEFINE_CLASS_QUERY(OpaqueInitializedAssertionPredicate)
DEFINE_CLASS_QUERY(OpaqueTemplateAssertionPredicate)
DEFINE_CLASS_QUERY(OpaqueLoopInit)

View File

@ -108,11 +108,7 @@ void OpaqueMultiversioningNode::dump_spec(outputStream *st) const {
}
#endif
const Type* OpaqueNotNullNode::Value(PhaseGVN* phase) const {
return phase->type(in(1));
}
const Type* OpaqueGuardNode::Value(PhaseGVN* phase) const {
const Type* OpaqueCheckNode::Value(PhaseGVN* phase) const {
return phase->type(in(1));
}

View File

@ -130,37 +130,25 @@ public:
// This node is used in the context of intrinsics. We sometimes implicitly know that an object is non-null even though
// the compiler cannot prove it. We therefore add a corresponding cast to propagate this implicit knowledge. However,
// this cast could become top during optimizations (input to cast becomes null) and the data path is folded. To ensure
// that the control path is also properly folded, we insert an If node with a OpaqueNotNullNode as condition. During
// macro expansion, we replace the OpaqueNotNullNodes with true in product builds such that the actually unneeded checks
// that the control path is also properly folded, we insert an If node with a OpaqueCheckNode as condition. During
// macro expansion, we replace the OpaqueCheckNodes with true in product builds such that the actually unneeded checks
// are folded and do not end up in the emitted code. In debug builds, we keep the actual checks as additional
// verification code (i.e. removing OpaqueNotNullNodes and use the BoolNode inputs instead). For more details, also see
// verification code (i.e. removing OpaqueCheckNodes and use the BoolNode inputs instead). For more details, also see
// GraphKit::must_be_not_null().
class OpaqueNotNullNode : public Node {
// Similarly, sometimes we know that a size or limit guard is checked (e.g. there is already a guard in the caller) but
// the compiler cannot prove it. We could in principle avoid adding a guard in the intrinsic but in some cases (e.g.
// when the input is a constant that breaks the guard and the caller guard is not inlined) the input of the intrinsic
// can become top and the data path is folded. To ensure that the control path is also properly folded, we insert an
// OpaqueCheckNode before the If node in the guard. During macro expansion, we replace the OpaqueCheckNode with false
// in product builds such that the actually unneeded guards are folded and do not end up in the emitted code. In debug
// builds, we keep the actual checks as additional verification code (i.e. removing OpaqueCheckNodes and use the
// BoolNode inputs instead).
class OpaqueCheckNode : public Node {
private:
bool _positive;
public:
OpaqueNotNullNode(Compile* C, Node* tst) : Node(nullptr, tst) {
init_class_id(Class_OpaqueNotNull);
init_flags(Flag_is_macro);
C->add_macro_node(this);
}
virtual int Opcode() const;
virtual const Type* Value(PhaseGVN* phase) const;
virtual const Type* bottom_type() const { return TypeInt::BOOL; }
};
// Similar to OpaqueNotNullNode but for guards. Sometimes we know that a size or limit guard is checked
// (e.g. there is already a guard in the caller) but the compiler cannot prove it. We could in principle avoid
// adding a guard in the intrinsic but in some cases (e.g. when the input is a constant that breaks the guard
// and the caller guard is not inlined) the input of the intrinsic can become top and the data path is folded.
// Similar to OpaqueNotNullNode to ensure that the control path is also properly folded, we insert a OpaqueGuardNode
// before the If node in the guard. During macro expansion, we replace the OpaqueGuardNode with false in product
// builds such that the actually unneeded guards are folded and do not end up in the emitted code. In debug builds,
// we keep the actual checks as additional verification code (i.e. removing OpaqueGuardNode and use the BoolNode
// inputs instead).
class OpaqueGuardNode : public Node {
public:
OpaqueGuardNode(Compile* C, Node* tst) : Node(nullptr, tst) {
init_class_id(Class_OpaqueGuard);
OpaqueCheckNode(Compile* C, Node* tst, bool positive) : Node(nullptr, tst), _positive(positive) {
init_class_id(Class_OpaqueCheck);
init_flags(Flag_is_macro);
C->add_macro_node(this);
}
@ -168,6 +156,7 @@ class OpaqueGuardNode : public Node {
virtual int Opcode() const;
virtual const Type* Value(PhaseGVN* phase) const;
virtual const Type* bottom_type() const { return TypeInt::BOOL; }
bool is_positive() { return _positive; }
};
// This node is used for Template Assertion Predicate BoolNodes. A Template Assertion Predicate is always removed

View File

@ -307,7 +307,7 @@ bool PhaseIdealLoop::clone_cmp_down(Node* n, const Node* blk1, const Node* blk2)
assert( bol->is_Bool(), "" );
if (bol->outcnt() == 1) {
Node* use = bol->unique_out();
if (use->is_OpaqueNotNull() || use->is_OpaqueGuard() || use->is_OpaqueTemplateAssertionPredicate() ||
if (use->is_OpaqueCheck() || use->is_OpaqueTemplateAssertionPredicate() ||
use->is_OpaqueInitializedAssertionPredicate()) {
if (use->outcnt() == 1) {
Node* iff = use->unique_out();
@ -331,8 +331,8 @@ bool PhaseIdealLoop::clone_cmp_down(Node* n, const Node* blk1, const Node* blk2)
// Recursively sink any BoolNode
for (DUIterator j = bol->outs(); bol->has_out(j); j++) {
Node* u = bol->out(j);
// Uses are either IfNodes, CMoves, OpaqueNotNull, OpaqueGuard or Opaque*AssertionPredicate
if (u->is_OpaqueNotNull() || u->is_OpaqueGuard() || u->is_OpaqueTemplateAssertionPredicate() ||
// Uses are either IfNodes, CMoves, OpaqueCheck or Opaque*AssertionPredicate
if (u->is_OpaqueCheck() || u->is_OpaqueTemplateAssertionPredicate() ||
u->is_OpaqueInitializedAssertionPredicate()) {
assert(u->in(1) == bol, "bad input");
for (DUIterator_Last kmin, k = u->last_outs(kmin); k >= kmin; --k) {

View File

@ -64,7 +64,7 @@ public class TestCanReduceCheckUsersDifferentIfs {
// (6) CastPP(phi1) ends up at IfFalse of OuterStripMinedLoopEnd of loop (L).
// (7) EA tries to reduce phi1(CheckCastPP(B), CheckCastPP(c)) and looks at
// OuterStripMinedLoopEnd and asserts that if it's not an IfNode that it has
// an OpaqueNotNull which obviously is not the case and the assert fails.
// an OpaqueCheck which obviously is not the case and the assert fails.
// (5) Found to be false after PhaseIdealLoop before EA and is folded away.
if (y == 76) {
@ -77,7 +77,7 @@ public class TestCanReduceCheckUsersDifferentIfs {
}
// Same as testOuterStripMinedLoopEnd() but we find in (7) a ParsePredicate from the
// removed loop (L) which also does not have an OpaqueNotNull and the assert fails.
// removed loop (L) which also does not have an OpaqueCheck and the assert fails.
static void testParsePredicate() {
A a = flag ? new B() : new C();

View File

@ -33,7 +33,7 @@ package compiler.intrinsics.string;
import compiler.lib.ir_framework.*;
public class TestOpaqueGuardNodes {
public class TestOpaqueCheckNodes {
static byte[] bytes = new byte[42];
@ -49,8 +49,8 @@ public class TestOpaqueGuardNodes {
}
@Test
@IR(counts = {IRNode.OPAQUE_GUARD, "3"}, phase = CompilePhase.AFTER_PARSING)
@IR(failOn = {IRNode.OPAQUE_GUARD}, phase = CompilePhase.AFTER_MACRO_EXPANSION)
@IR(counts = {IRNode.OPAQUE_CHECK, "3"}, phase = CompilePhase.AFTER_PARSING)
@IR(failOn = {IRNode.OPAQUE_CHECK}, phase = CompilePhase.AFTER_MACRO_EXPANSION)
@Arguments(setup = "setup")
private static String test(byte[] bytes, int i, int l) {
return new String(bytes, i , l);

View File

@ -3137,9 +3137,9 @@ public class IRNode {
machOnlyNameRegex(REPLICATE_HF_IMM8, "replicateHF_imm8_gt128b");
}
public static final String OPAQUE_GUARD = PREFIX + "OPAQUE_GUARD" + POSTFIX;
public static final String OPAQUE_CHECK = PREFIX + "OPAQUE_CHECK" + POSTFIX;
static {
beforeMatchingNameRegex(OPAQUE_GUARD, "OpaqueGuard");
beforeMatchingNameRegex(OPAQUE_CHECK, "OpaqueCheck");
}
/*

View File

@ -65,15 +65,15 @@ public class OpaqueAccesses {
// Finish the line after the node type, skips full line, and eats until before the node types
private static final String SKIP = IRNode.MID + IRNode.END + "\\R" + FULL_LINES + "\\s*" + IRNode.START;
private static final String CALL_STATIC_JAVA_AND_THEN_OPAQUE_NOT_NULL = IRNode.START + "CallStaticJava" + SKIP + "OpaqueNotNull" + IRNode.MID + IRNode.END;
private static final String OPAQUE_NOT_NULL_AND_THEN_CALL_STATIC_JAVA = IRNode.START + "OpaqueNotNull" + SKIP + "CallStaticJava" + IRNode.MID + IRNode.END;
/* Having both CallStaticJava and OpaqueNotNull, in any order. We use that in a failOn to make sure we have one
private static final String CALL_STATIC_JAVA_AND_THEN_OPAQUE_CHECK = IRNode.START + "CallStaticJava" + SKIP + "OpaqueCheck" + IRNode.MID + IRNode.END;
private static final String OPAQUE_CHECK_AND_THEN_CALL_STATIC_JAVA = IRNode.START + "OpaqueCheck" + SKIP + "CallStaticJava" + IRNode.MID + IRNode.END;
/* Having both CallStaticJava and OpaqueCheck, in any order. We use that in a failOn to make sure we have one
* or the other (or none), but not both.
* The CallStaticJava happens when the call is not intrinsified, and the OpaqueNotNull comes from the intrinsic.
* The CallStaticJava happens when the call is not intrinsified, and the OpaqueCheck comes from the intrinsic.
* We don't want a unfinished intrinsic, with the call nevertheless.
*/
private static final String BOTH_CALL_STATIC_JAVA_AND_OPAQUE_NOT_NULL =
"(" + CALL_STATIC_JAVA_AND_THEN_OPAQUE_NOT_NULL + ") | (" + OPAQUE_NOT_NULL_AND_THEN_CALL_STATIC_JAVA + ")";
"(" + CALL_STATIC_JAVA_AND_THEN_OPAQUE_CHECK + ") | (" + OPAQUE_CHECK_AND_THEN_CALL_STATIC_JAVA + ")";
@Test