From 775e1e7bfd8a7fa084adafb76dfe2738f54918fc Mon Sep 17 00:00:00 2001 From: rwestrel Date: Thu, 12 Feb 2026 17:16:18 +0100 Subject: [PATCH 01/11] fix & test --- .../inlinetypes/TestEAScalarizedArg.java | 14 ++++++ .../inlinetypes/TestOptimizePtrCompare.java | 43 +++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestEAScalarizedArg.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestEAScalarizedArg.java index 330e23aa391..eca8246f0af 100644 --- a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestEAScalarizedArg.java +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestEAScalarizedArg.java @@ -53,6 +53,20 @@ static int test1(Object o) { static Object notInlined(MyValue arg1, Object arg2) { return arg2; } + + static int test2() { + Object o2 = new Object(); + MyValue v = new MyValue(null); + Object res = notInlined2(v); + if (res == null) { + return 1; + } + return 2; + } + + static Object notInlined2(MyValue arg1) { + return arg1; + } static int test2() { Object o2 = new Object(); diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestOptimizePtrCompare.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestOptimizePtrCompare.java index b2a3ce30357..7a00996c203 100644 --- a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestOptimizePtrCompare.java +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestOptimizePtrCompare.java @@ -78,4 +78,47 @@ public static void test2() { static MyValue notInlined2(MyValue v) { return v; } + static value class MyValue2 { + Object o1; + Object o2; + + MyValue2(Object o1, Object o2) { + this.o1 = o1; + this.o2 = o2; + } + } + + static Object fieldO = new Object(); + + // @Test + // @IR(failOn = {IRNode.CMP_P}) + // public static void test3() { + // Object notUsed = new Object(); // make sure EA runs + // MyValue2 arg = new MyValue2(null, fieldO); + // MyValue2 res = notInlined3(arg); + // if (res.o1 != null) { + // throw new RuntimeException("never taken"); + // } + // } + + // @DontInline + // static MyValue2 notInlined3(MyValue2 v) { + // return v; + // } + + @Test + @IR(failOn = {IRNode.CMP_P}) + public static void test4() { + Object notUsed = new Object(); // make sure EA runs + MyValue arg = new MyValue(null); + Object res = notInlined3(arg); + if (res == null) { + throw new RuntimeException("never taken"); + } + } + + @DontInline + static Object notInlined3(MyValue v) { + return v; + } } From 8711fbf72c3fe541bc2a4ab893aae6ec7c7a6978 Mon Sep 17 00:00:00 2001 From: rwestrel Date: Fri, 13 Feb 2026 15:17:53 +0100 Subject: [PATCH 02/11] more --- .../inlinetypes/TestOptimizePtrCompare.java | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestOptimizePtrCompare.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestOptimizePtrCompare.java index 7a00996c203..056be777ec1 100644 --- a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestOptimizePtrCompare.java +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestOptimizePtrCompare.java @@ -90,24 +90,24 @@ static value class MyValue2 { static Object fieldO = new Object(); - // @Test - // @IR(failOn = {IRNode.CMP_P}) - // public static void test3() { - // Object notUsed = new Object(); // make sure EA runs - // MyValue2 arg = new MyValue2(null, fieldO); - // MyValue2 res = notInlined3(arg); - // if (res.o1 != null) { - // throw new RuntimeException("never taken"); - // } - // } + @Test + @IR(failOn = {IRNode.CMP_P}) + public static void test3() { + Object notUsed = new Object(); // make sure EA runs + MyValue2 arg = new MyValue2(null, fieldO); + MyValue2 res = notInlined3(arg); + if (res.o1 != null) { + throw new RuntimeException("never taken"); + } + } - // @DontInline - // static MyValue2 notInlined3(MyValue2 v) { - // return v; - // } + @DontInline + static MyValue2 notInlined3(MyValue2 v) { + return v; + } @Test - @IR(failOn = {IRNode.CMP_P}) + @IR(counts = {IRNode.CMP_P, "1"}) public static void test4() { Object notUsed = new Object(); // make sure EA runs MyValue arg = new MyValue(null); From bc8d3a092c0851f273d1a76aceed79c6f02181ce Mon Sep 17 00:00:00 2001 From: rwestrel Date: Fri, 13 Feb 2026 15:24:54 +0100 Subject: [PATCH 03/11] merge --- .../valhalla/inlinetypes/TestEAScalarizedArg.java | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestEAScalarizedArg.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestEAScalarizedArg.java index eca8246f0af..330e23aa391 100644 --- a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestEAScalarizedArg.java +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestEAScalarizedArg.java @@ -53,20 +53,6 @@ static int test1(Object o) { static Object notInlined(MyValue arg1, Object arg2) { return arg2; } - - static int test2() { - Object o2 = new Object(); - MyValue v = new MyValue(null); - Object res = notInlined2(v); - if (res == null) { - return 1; - } - return 2; - } - - static Object notInlined2(MyValue arg1) { - return arg1; - } static int test2() { Object o2 = new Object(); From 59135a7f779d384e030b9f04cbb67afca44e7ac1 Mon Sep 17 00:00:00 2001 From: rwestrel Date: Fri, 13 Feb 2026 17:57:22 +0100 Subject: [PATCH 04/11] more --- src/hotspot/share/opto/escape.cpp | 54 ++++++++++++++----- .../inlinetypes/TestOptimizePtrCompare.java | 22 ++++++-- 2 files changed, 61 insertions(+), 15 deletions(-) diff --git a/src/hotspot/share/opto/escape.cpp b/src/hotspot/share/opto/escape.cpp index 043f590bc83..38f71971408 100644 --- a/src/hotspot/share/opto/escape.cpp +++ b/src/hotspot/share/opto/escape.cpp @@ -1749,14 +1749,18 @@ void ConnectionGraph::add_node_to_connection_graph(Node *n, Unique_Node_List *de break; case Op_Proj: { // we are only interested in the oop result projection from a call - if (n->as_Proj()->_con >= TypeFunc::Parms && n->in(0)->is_Call() && - (n->in(0)->as_Call()->returns_pointer() || n->bottom_type()->isa_ptr())) { - assert((n->as_Proj()->_con == TypeFunc::Parms && n->in(0)->as_Call()->returns_pointer()) || - n->in(0)->as_Call()->tf()->returns_inline_type_as_fields(), "what kind of oop return is it?"); + if (n->as_Proj()->_con == TypeFunc::Parms && n->in(0)->is_Call() && n->in(0)->as_Call()->returns_pointer()) { add_local_var_and_edge(n, PointsToNode::NoEscape, n->in(0), delayed_worklist); } else if (n->as_Proj()->_con >= TypeFunc::Parms && n->in(0)->is_LoadFlat() && igvn->type(n)->isa_ptr()) { // Treat LoadFlat outputs similar to a call return value add_local_var_and_edge(n, PointsToNode::NoEscape, n->in(0), delayed_worklist); + } else if (n->as_Proj()->_con >= TypeFunc::Parms && n->in(0)->is_Call() && n->bottom_type()->isa_ptr()) { + assert(n->in(0)->as_Call()->tf()->returns_inline_type_as_fields(), ""); + if (n->as_Proj()->_con == TypeFunc::Parms) { + add_local_var_and_edge(n, PointsToNode::NoEscape, n->in(0), nullptr); + } else { + add_local_var(n, PointsToNode::NoEscape); + } } break; } @@ -1927,14 +1931,18 @@ void ConnectionGraph::add_final_edges(Node *n) { break; } case Op_Proj: { - if (n->in(0)->is_Call()) { - // we are only interested in the oop result projection from a call - assert((n->as_Proj()->_con == TypeFunc::Parms && n->in(0)->as_Call()->returns_pointer()) || - n->in(0)->as_Call()->tf()->returns_inline_type_as_fields(), "what kind of oop return is it?"); + if (n->as_Proj()->_con == TypeFunc::Parms && n->in(0)->is_Call() && n->in(0)->as_Call()->returns_pointer()) { add_local_var_and_edge(n, PointsToNode::NoEscape, n->in(0), nullptr); } else if (n->in(0)->is_LoadFlat()) { // Treat LoadFlat outputs similar to a call return value add_local_var_and_edge(n, PointsToNode::NoEscape, n->in(0), nullptr); + } else if (n->as_Proj()->_con >= TypeFunc::Parms && n->in(0)->is_Call() && n->bottom_type()->isa_ptr()) { + assert(n->in(0)->as_Call()->tf()->returns_inline_type_as_fields(), ""); + if (n->as_Proj()->_con == TypeFunc::Parms) { + add_local_var_and_edge(n, PointsToNode::NoEscape, n->in(0), nullptr); + } else { + add_local_var(n, PointsToNode::NoEscape); + } } break; } @@ -2129,6 +2137,7 @@ class DomainIterator : public StackObj { uint _i_domain_cc; int _i_sig_cc; uint _depth; + int _first_field_pos; void next_helper() { if (_sig_cc == nullptr) { @@ -2139,6 +2148,9 @@ class DomainIterator : public StackObj { BasicType bt = _sig_cc->at(_i_sig_cc)._bt; assert(bt != T_VOID || _sig_cc->at(_i_sig_cc-1)._bt == prev_bt, ""); if (bt == T_METADATA) { + if (_depth == 0) { + _first_field_pos = _i_sig_cc; + } _depth++; } else if (bt == T_VOID && (prev_bt != T_LONG && prev_bt != T_DOUBLE)) { _depth--; @@ -2162,7 +2174,8 @@ class DomainIterator : public StackObj { _i_domain(TypeFunc::Parms), _i_domain_cc(TypeFunc::Parms), _i_sig_cc(0), - _depth(0) { + _depth(0), + _first_field_pos(-1) { next_helper(); } @@ -2197,6 +2210,12 @@ class DomainIterator : public StackObj { const Type* current_domain_cc() const { return _domain_cc->field_at(_i_domain_cc); } + + int first_field_pos() const { + assert(_first_field_pos != -1, ""); + return _first_field_pos; + } + }; void ConnectionGraph::add_call_node(CallNode* call) { @@ -2528,10 +2547,21 @@ void ConnectionGraph::process_call_arguments(CallNode *call) { Node* arg = call->in(di.i_domain_cc()); PointsToNode* arg_ptn = ptnode_adr(arg->_idx); if (at->isa_ptr() != nullptr && - call_analyzer->is_arg_returned(k) && - !meth->is_scalarized_arg(k)) { + call_analyzer->is_arg_returned(k) ) { // The call returns arguments. - if (call_ptn != nullptr) { // Is call's result used? + if (meth->is_scalarized_arg(k)) { + ProjNode* res_proj = call->proj_out_or_null(di.i_domain_cc() - di.first_field_pos()); + if (res_proj != nullptr) { + assert(_igvn->type(res_proj)->isa_ptr(), ""); + if (res_proj->_con == TypeFunc::Parms) { + assert(call_ptn->is_LocalVar(), "node should be registered"); + assert(arg_ptn != nullptr, "node should be registered"); + add_edge(call_ptn, arg_ptn); + } else { + add_edge(ptnode_adr(res_proj->_idx), arg_ptn); + } + } + } else if (call_ptn != nullptr) { // Is call's result used? assert(call_ptn->is_LocalVar(), "node should be registered"); assert(arg_ptn != nullptr, "node should be registered"); add_edge(call_ptn, arg_ptn); diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestOptimizePtrCompare.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestOptimizePtrCompare.java index 056be777ec1..31059dd87f1 100644 --- a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestOptimizePtrCompare.java +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestOptimizePtrCompare.java @@ -64,7 +64,7 @@ static value class MyValue { } @Test - @IR(counts = {IRNode.CMP_P, "1"}) + @IR(failOn = {IRNode.CMP_P}) public static void test2() { Object notUsed = new Object(); // make sure EA runs MyValue arg = new MyValue(null); @@ -111,14 +111,30 @@ static MyValue2 notInlined3(MyValue2 v) { public static void test4() { Object notUsed = new Object(); // make sure EA runs MyValue arg = new MyValue(null); - Object res = notInlined3(arg); + Object res = notInlined4(arg); if (res == null) { throw new RuntimeException("never taken"); } } @DontInline - static Object notInlined3(MyValue v) { + static Object notInlined4(MyValue v) { + return v; + } + + @Test + @IR(failOn = {IRNode.CMP_P}) + public static void test5() { + Object notUsed = new Object(); // make sure EA runs + MyValue2 arg = new MyValue2(fieldO, null); + MyValue2 res = notInlined5(arg); + if (res.o2 != null) { + throw new RuntimeException("never taken"); + } + } + + @DontInline + static MyValue2 notInlined5(MyValue2 v) { return v; } } From 3380b675bbcd1554a63f3ffafa3e9aaae2a2c1d5 Mon Sep 17 00:00:00 2001 From: rwestrel Date: Thu, 26 Feb 2026 10:18:29 +0100 Subject: [PATCH 05/11] more --- src/hotspot/share/opto/escape.cpp | 278 +++++++++--------- src/hotspot/share/opto/escape.hpp | 2 + .../compiler/lib/ir_framework/IRNode.java | 5 + .../inlinetypes/TestOptimizePtrCompare.java | 27 +- 4 files changed, 171 insertions(+), 141 deletions(-) diff --git a/src/hotspot/share/opto/escape.cpp b/src/hotspot/share/opto/escape.cpp index 38f71971408..7ae5c7cb493 100644 --- a/src/hotspot/share/opto/escape.cpp +++ b/src/hotspot/share/opto/escape.cpp @@ -1586,6 +1586,37 @@ void ConnectionGraph::add_objload_to_connection_graph(Node *n, Unique_Node_List } } +void ConnectionGraph::add_proj(Node* n, Unique_Node_List* delayed_worklist) { + if (n->as_Proj()->_con == TypeFunc::Parms && n->in(0)->is_Call() && n->in(0)->as_Call()->returns_pointer()) { + add_local_var_and_edge(n, PointsToNode::NoEscape, n->in(0), delayed_worklist); + } else if (n->in(0)->is_LoadFlat()) { + // Treat LoadFlat outputs similar to a call return value + add_local_var_and_edge(n, PointsToNode::NoEscape, n->in(0), delayed_worklist); + } else if (n->as_Proj()->_con >= TypeFunc::Parms && n->in(0)->is_Call() && n->bottom_type()->isa_ptr()) { + CallNode* call = n->in(0)->as_Call(); + assert(call->tf()->returns_inline_type_as_fields(), ""); + ciMethod* meth = n->in(0)->as_CallJava()->method(); + BCEscapeAnalyzer* call_analyzer = (meth != nullptr) ? meth->get_bcea() : nullptr; + bool ret_arg = false; + if (call_analyzer != nullptr) { + const TypeTuple* d = call->tf()->domain_sig(); + for (uint i = TypeFunc::Parms; i < d->cnt(); i++) { + if (d->field_at(i)->isa_ptr() != nullptr && + call_analyzer->is_arg_returned(i - TypeFunc::Parms) && + meth->is_scalarized_arg(i - TypeFunc::Parms)) { + ret_arg = true; + break; + } + } + } + if (n->as_Proj()->_con == TypeFunc::Parms || !ret_arg) { + add_local_var_and_edge(n, PointsToNode::NoEscape, n->in(0), delayed_worklist); + } else { + add_local_var(n, PointsToNode::NoEscape); + } + } +} + // Populate Connection Graph with PointsTo nodes and create simple // connection graph edges. void ConnectionGraph::add_node_to_connection_graph(Node *n, Unique_Node_List *delayed_worklist) { @@ -1749,19 +1780,7 @@ void ConnectionGraph::add_node_to_connection_graph(Node *n, Unique_Node_List *de break; case Op_Proj: { // we are only interested in the oop result projection from a call - if (n->as_Proj()->_con == TypeFunc::Parms && n->in(0)->is_Call() && n->in(0)->as_Call()->returns_pointer()) { - add_local_var_and_edge(n, PointsToNode::NoEscape, n->in(0), delayed_worklist); - } else if (n->as_Proj()->_con >= TypeFunc::Parms && n->in(0)->is_LoadFlat() && igvn->type(n)->isa_ptr()) { - // Treat LoadFlat outputs similar to a call return value - add_local_var_and_edge(n, PointsToNode::NoEscape, n->in(0), delayed_worklist); - } else if (n->as_Proj()->_con >= TypeFunc::Parms && n->in(0)->is_Call() && n->bottom_type()->isa_ptr()) { - assert(n->in(0)->as_Call()->tf()->returns_inline_type_as_fields(), ""); - if (n->as_Proj()->_con == TypeFunc::Parms) { - add_local_var_and_edge(n, PointsToNode::NoEscape, n->in(0), nullptr); - } else { - add_local_var(n, PointsToNode::NoEscape); - } - } + add_proj(n, delayed_worklist); break; } case Op_Rethrow: // Exception object escapes @@ -1833,6 +1852,107 @@ void ConnectionGraph::add_node_to_connection_graph(Node *n, Unique_Node_List *de return; } +// Iterate over the domains for the scalarized and non scalarized calling conventions: Only move to the next element +// in the non scalarized calling convention once all elements of the scalarized calling convention for that parameter +// have been iterated over. So (ignoring hidden arguments such as the null marker) iterating over: +// value class MyValue { +// int f1; +// float f2; +// } +// void m(Object o, MyValue v, int i) +// produces the pairs: +// (Object, Object), (Myvalue, int), (MyValue, float), (int, int) +class DomainIterator : public StackObj { +private: + const TypeTuple* _domain; + const TypeTuple* _domain_cc; + const GrowableArray* _sig_cc; + + uint _i_domain; + uint _i_domain_cc; + int _i_sig_cc; + uint _depth; + uint _first_field_pos; + + void next_helper() { + if (_sig_cc == nullptr) { + return; + } + BasicType prev_bt = _i_sig_cc > 0 ? _sig_cc->at(_i_sig_cc-1)._bt : T_ILLEGAL; + while (_i_sig_cc < _sig_cc->length()) { + BasicType bt = _sig_cc->at(_i_sig_cc)._bt; + assert(bt != T_VOID || _sig_cc->at(_i_sig_cc-1)._bt == prev_bt, ""); + if (bt == T_METADATA) { + if (_depth == 0) { + _first_field_pos = _i_domain_cc; + } + _depth++; + } else if (bt == T_VOID && (prev_bt != T_LONG && prev_bt != T_DOUBLE)) { + _depth--; + if (_depth == 0) { + _i_domain++; + } + } else { + return; + } + prev_bt = bt; + _i_sig_cc++; + } + } + +public: + + DomainIterator(CallJavaNode* call) : + _domain(call->tf()->domain_sig()), + _domain_cc(call->tf()->domain_cc()), + _sig_cc(call->method()->get_sig_cc()), + _i_domain(TypeFunc::Parms), + _i_domain_cc(TypeFunc::Parms), + _i_sig_cc(0), + _depth(0), + _first_field_pos(0) { + next_helper(); + } + + bool has_next() const { + assert(_sig_cc == nullptr || (_i_sig_cc < _sig_cc->length()) == (_i_domain < _domain->cnt()), "should reach end in sync"); + assert((_i_domain < _domain->cnt()) == (_i_domain_cc < _domain_cc->cnt()), "should reach end in sync"); + return _i_domain < _domain->cnt(); + } + + void next() { + assert(_depth != 0 || _domain->field_at(_i_domain) == _domain_cc->field_at(_i_domain_cc), "should produce same non scalarized elements"); + _i_sig_cc++; + if (_depth == 0) { + _i_domain++; + } + _i_domain_cc++; + next_helper(); + } + + uint i_domain() const { + return _i_domain; + } + + uint i_domain_cc() const { + return _i_domain_cc; + } + + const Type* current_domain() const { + return _domain->field_at(_i_domain); + } + + const Type* current_domain_cc() const { + return _domain_cc->field_at(_i_domain_cc); + } + + uint first_field_pos() const { + assert(_first_field_pos >= TypeFunc::Parms, ""); + return _first_field_pos; + } + +}; + // Add final simple edges to graph. void ConnectionGraph::add_final_edges(Node *n) { PointsToNode* n_ptn = ptnode_adr(n->_idx); @@ -1931,19 +2051,7 @@ void ConnectionGraph::add_final_edges(Node *n) { break; } case Op_Proj: { - if (n->as_Proj()->_con == TypeFunc::Parms && n->in(0)->is_Call() && n->in(0)->as_Call()->returns_pointer()) { - add_local_var_and_edge(n, PointsToNode::NoEscape, n->in(0), nullptr); - } else if (n->in(0)->is_LoadFlat()) { - // Treat LoadFlat outputs similar to a call return value - add_local_var_and_edge(n, PointsToNode::NoEscape, n->in(0), nullptr); - } else if (n->as_Proj()->_con >= TypeFunc::Parms && n->in(0)->is_Call() && n->bottom_type()->isa_ptr()) { - assert(n->in(0)->as_Call()->tf()->returns_inline_type_as_fields(), ""); - if (n->as_Proj()->_con == TypeFunc::Parms) { - add_local_var_and_edge(n, PointsToNode::NoEscape, n->in(0), nullptr); - } else { - add_local_var(n, PointsToNode::NoEscape); - } - } + add_proj(n, nullptr); break; } case Op_Rethrow: // Exception object escapes @@ -2117,106 +2225,6 @@ bool ConnectionGraph::add_final_edges_unsafe_access(Node* n, uint opcode) { return false; } -// Iterate over the domains for the scalarized and non scalarized calling conventions: Only move to the next element -// in the non scalarized calling convention once all elements of the scalarized calling convention for that parameter -// have been iterated over. So (ignoring hidden arguments such as the null marker) iterating over: -// value class MyValue { -// int f1; -// float f2; -// } -// void m(Object o, MyValue v, int i) -// produces the pairs: -// (Object, Object), (Myvalue, int), (MyValue, float), (int, int) -class DomainIterator : public StackObj { -private: - const TypeTuple* _domain; - const TypeTuple* _domain_cc; - const GrowableArray* _sig_cc; - - uint _i_domain; - uint _i_domain_cc; - int _i_sig_cc; - uint _depth; - int _first_field_pos; - - void next_helper() { - if (_sig_cc == nullptr) { - return; - } - BasicType prev_bt = _i_sig_cc > 0 ? _sig_cc->at(_i_sig_cc-1)._bt : T_ILLEGAL; - while (_i_sig_cc < _sig_cc->length()) { - BasicType bt = _sig_cc->at(_i_sig_cc)._bt; - assert(bt != T_VOID || _sig_cc->at(_i_sig_cc-1)._bt == prev_bt, ""); - if (bt == T_METADATA) { - if (_depth == 0) { - _first_field_pos = _i_sig_cc; - } - _depth++; - } else if (bt == T_VOID && (prev_bt != T_LONG && prev_bt != T_DOUBLE)) { - _depth--; - if (_depth == 0) { - _i_domain++; - } - } else { - return; - } - prev_bt = bt; - _i_sig_cc++; - } - } - -public: - - DomainIterator(CallJavaNode* call) : - _domain(call->tf()->domain_sig()), - _domain_cc(call->tf()->domain_cc()), - _sig_cc(call->method()->get_sig_cc()), - _i_domain(TypeFunc::Parms), - _i_domain_cc(TypeFunc::Parms), - _i_sig_cc(0), - _depth(0), - _first_field_pos(-1) { - next_helper(); - } - - bool has_next() const { - assert(_sig_cc == nullptr || (_i_sig_cc < _sig_cc->length()) == (_i_domain < _domain->cnt()), "should reach end in sync"); - assert((_i_domain < _domain->cnt()) == (_i_domain_cc < _domain_cc->cnt()), "should reach end in sync"); - return _i_domain < _domain->cnt(); - } - - void next() { - assert(_depth != 0 || _domain->field_at(_i_domain) == _domain_cc->field_at(_i_domain_cc), "should produce same non scalarized elements"); - _i_sig_cc++; - if (_depth == 0) { - _i_domain++; - } - _i_domain_cc++; - next_helper(); - } - - uint i_domain() const { - return _i_domain; - } - - uint i_domain_cc() const { - return _i_domain_cc; - } - - const Type* current_domain() const { - return _domain->field_at(_i_domain); - } - - const Type* current_domain_cc() const { - return _domain_cc->field_at(_i_domain_cc); - } - - int first_field_pos() const { - assert(_first_field_pos != -1, ""); - return _first_field_pos; - } - -}; void ConnectionGraph::add_call_node(CallNode* call) { assert(call->returns_pointer() || call->tf()->returns_inline_type_as_fields(), "only for call which returns pointer"); @@ -2329,11 +2337,11 @@ void ConnectionGraph::add_call_node(CallNode* call) { } else { bool ret_arg = false; // Determine whether any arguments are returned. - for (DomainIterator di(call->as_CallJava()); di.has_next(); di.next()) { - uint arg = di.i_domain() - TypeFunc::Parms; - if (di.current_domain_cc()->isa_ptr() != nullptr && - call_analyzer->is_arg_returned(arg) && - !meth->is_scalarized_arg(arg)) { + const TypeTuple* d = call->tf()->domain_sig(); + for (uint i = TypeFunc::Parms; i < d->cnt(); i++) { + if (d->field_at(i)->isa_ptr() != nullptr && + call_analyzer->is_arg_returned(i - TypeFunc::Parms) && + !meth->is_scalarized_arg(i - TypeFunc::Parms)) { ret_arg = true; break; } @@ -2550,13 +2558,13 @@ void ConnectionGraph::process_call_arguments(CallNode *call) { call_analyzer->is_arg_returned(k) ) { // The call returns arguments. if (meth->is_scalarized_arg(k)) { - ProjNode* res_proj = call->proj_out_or_null(di.i_domain_cc() - di.first_field_pos()); + ProjNode* res_proj = call->proj_out_or_null(di.i_domain_cc() - di.first_field_pos() + TypeFunc::Parms); if (res_proj != nullptr) { assert(_igvn->type(res_proj)->isa_ptr(), ""); if (res_proj->_con == TypeFunc::Parms) { - assert(call_ptn->is_LocalVar(), "node should be registered"); - assert(arg_ptn != nullptr, "node should be registered"); - add_edge(call_ptn, arg_ptn); + // assert(call_ptn->is_LocalVar(), "node should be registered"); + // assert(arg_ptn != nullptr, "node should be registered"); + // add_edge(call_ptn, arg_ptn); } else { add_edge(ptnode_adr(res_proj->_idx), arg_ptn); } diff --git a/src/hotspot/share/opto/escape.hpp b/src/hotspot/share/opto/escape.hpp index 36da0560fc6..c04d451331c 100644 --- a/src/hotspot/share/opto/escape.hpp +++ b/src/hotspot/share/opto/escape.hpp @@ -665,6 +665,8 @@ class ConnectionGraph: public ArenaObj { // Utility function for nodes that load an object void add_objload_to_connection_graph(Node* n, Unique_Node_List* delayed_worklist); + void add_proj(Node* n, Unique_Node_List* delayed_worklist); + // Add LocalVar node and edge if possible void add_local_var_and_edge(Node* n, PointsToNode::EscapeState es, Node* to, Unique_Node_List *delayed_worklist) { diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java index eae34c42968..c09c3764672 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java @@ -653,6 +653,11 @@ public static void callLeafNoFpOfMethodNodes(String irNodePlaceholder, String ca beforeMatchingNameRegex(CMP_N, "CmpN"); } + public static final String CMP_P_OR_N = PREFIX + "CMP_P_OR_N" + POSTFIX; + static { + beforeMatchingNameRegex(CMP_P_OR_N, "Cmp(P|N)"); + } + public static final String CMP_LT_MASK = PREFIX + "CMP_LT_MASK" + POSTFIX; static { beforeMatchingNameRegex(CMP_LT_MASK, "CmpLTMask"); diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestOptimizePtrCompare.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestOptimizePtrCompare.java index 31059dd87f1..1d2191a5f88 100644 --- a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestOptimizePtrCompare.java +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestOptimizePtrCompare.java @@ -36,11 +36,14 @@ public class TestOptimizePtrCompare { public static void main(String[] args) { - TestFramework.runWithFlags("--enable-preview"); + TestFramework.runWithFlags("--enable-preview", "-XX:+InlineTypePassFieldsAsArgs", "-XX:+InlineTypeReturnedAsFields"); + TestFramework.runWithFlags("--enable-preview", "-XX:-InlineTypePassFieldsAsArgs", "-XX:+InlineTypeReturnedAsFields"); + TestFramework.runWithFlags("--enable-preview", "-XX:+InlineTypePassFieldsAsArgs", "-XX:-InlineTypeReturnedAsFields"); + TestFramework.runWithFlags("--enable-preview", "-XX:-InlineTypePassFieldsAsArgs", "-XX:-InlineTypeReturnedAsFields"); } @Test - @IR(failOn = {IRNode.CMP_P}) + @IR(failOn = {IRNode.CMP_P_OR_N}) public static void test1() { Object notUsed = new Object(); // make sure EA runs Object arg = null; @@ -64,7 +67,10 @@ static value class MyValue { } @Test - @IR(failOn = {IRNode.CMP_P}) + @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "true", "InlineTypeReturnedAsFields", "true"}, failOn = {IRNode.CMP_P_OR_N}) + @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "false", "InlineTypeReturnedAsFields", "true"}, counts = {IRNode.CMP_P_OR_N, "2", IRNode.CMP_I, "1"}) + @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "true", "InlineTypeReturnedAsFields", "false"}, counts = {IRNode.CMP_P_OR_N, "2"}) + @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "false", "InlineTypeReturnedAsFields", "false"}, counts = {IRNode.CMP_P_OR_N, "3"}) public static void test2() { Object notUsed = new Object(); // make sure EA runs MyValue arg = new MyValue(null); @@ -91,7 +97,10 @@ static value class MyValue2 { static Object fieldO = new Object(); @Test - @IR(failOn = {IRNode.CMP_P}) + @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "true", "InlineTypeReturnedAsFields", "true"}, failOn = {IRNode.CMP_P_OR_N}) + @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "false", "InlineTypeReturnedAsFields", "true"}, counts = {IRNode.CMP_P_OR_N, "2", IRNode.CMP_I, "1"}) + @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "true", "InlineTypeReturnedAsFields", "false"}, counts = {IRNode.CMP_P_OR_N, "2"}) + @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "false", "InlineTypeReturnedAsFields", "false"}, counts = {IRNode.CMP_P_OR_N, "3"}) public static void test3() { Object notUsed = new Object(); // make sure EA runs MyValue2 arg = new MyValue2(null, fieldO); @@ -107,7 +116,10 @@ static MyValue2 notInlined3(MyValue2 v) { } @Test - @IR(counts = {IRNode.CMP_P, "1"}) + @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "true", "InlineTypeReturnedAsFields", "true"}, counts = {IRNode.CMP_P_OR_N, "1"}) + @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "false", "InlineTypeReturnedAsFields", "true"}, counts = {IRNode.CMP_P_OR_N, "2"}) + @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "true", "InlineTypeReturnedAsFields", "false"}, counts = {IRNode.CMP_P_OR_N, "1"}) + @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "false", "InlineTypeReturnedAsFields", "false"}, counts = {IRNode.CMP_P_OR_N, "2"}) public static void test4() { Object notUsed = new Object(); // make sure EA runs MyValue arg = new MyValue(null); @@ -123,7 +135,10 @@ static Object notInlined4(MyValue v) { } @Test - @IR(failOn = {IRNode.CMP_P}) + @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "true", "InlineTypeReturnedAsFields", "true"}, failOn = {IRNode.CMP_P_OR_N}) + @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "false", "InlineTypeReturnedAsFields", "true"}, counts = {IRNode.CMP_P_OR_N, "2", IRNode.CMP_I, "1"}) + @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "true", "InlineTypeReturnedAsFields", "false"}, counts = {IRNode.CMP_P_OR_N, "2"}) + @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "false", "InlineTypeReturnedAsFields", "false"}, counts = {IRNode.CMP_P_OR_N, "3"}) public static void test5() { Object notUsed = new Object(); // make sure EA runs MyValue2 arg = new MyValue2(fieldO, null); From 8f5e4eacf65f516bbd6f46b602d0b891ccf5b044 Mon Sep 17 00:00:00 2001 From: rwestrel Date: Fri, 27 Feb 2026 16:14:23 +0100 Subject: [PATCH 06/11] more --- src/hotspot/share/opto/escape.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/hotspot/share/opto/escape.cpp b/src/hotspot/share/opto/escape.cpp index 7ae5c7cb493..465307c86b7 100644 --- a/src/hotspot/share/opto/escape.cpp +++ b/src/hotspot/share/opto/escape.cpp @@ -1873,6 +1873,8 @@ class DomainIterator : public StackObj { int _i_sig_cc; uint _depth; uint _first_field_pos; + const bool _is_static; + uint _null_markers; void next_helper() { if (_sig_cc == nullptr) { @@ -1892,6 +1894,9 @@ class DomainIterator : public StackObj { if (_depth == 0) { _i_domain++; } + } else if (bt == T_BOOLEAN && prev_bt == T_METADATA && (_is_static || _i_domain > 0)) { + _null_markers++; + return; } else { return; } @@ -1910,7 +1915,9 @@ class DomainIterator : public StackObj { _i_domain_cc(TypeFunc::Parms), _i_sig_cc(0), _depth(0), - _first_field_pos(0) { + _first_field_pos(0), + _is_static(call->method()->is_static()), + _null_markers(0) { next_helper(); } @@ -1951,6 +1958,9 @@ class DomainIterator : public StackObj { return _first_field_pos; } + uint null_markers() const { + return _null_markers; + } }; // Add final simple edges to graph. @@ -2558,7 +2568,7 @@ void ConnectionGraph::process_call_arguments(CallNode *call) { call_analyzer->is_arg_returned(k) ) { // The call returns arguments. if (meth->is_scalarized_arg(k)) { - ProjNode* res_proj = call->proj_out_or_null(di.i_domain_cc() - di.first_field_pos() + TypeFunc::Parms); + ProjNode* res_proj = call->proj_out_or_null(di.i_domain_cc() - di.first_field_pos() - di.null_markers() + TypeFunc::Parms + 1); if (res_proj != nullptr) { assert(_igvn->type(res_proj)->isa_ptr(), ""); if (res_proj->_con == TypeFunc::Parms) { From 73d65ba99980bac7f1dbb852586a2bd7cabfa78e Mon Sep 17 00:00:00 2001 From: rwestrel Date: Fri, 27 Feb 2026 17:43:22 +0100 Subject: [PATCH 07/11] more --- src/hotspot/share/opto/escape.cpp | 8 +- .../inlinetypes/TestOptimizePtrCompare.java | 78 +++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/src/hotspot/share/opto/escape.cpp b/src/hotspot/share/opto/escape.cpp index 465307c86b7..cb27f87533f 100644 --- a/src/hotspot/share/opto/escape.cpp +++ b/src/hotspot/share/opto/escape.cpp @@ -1887,6 +1887,7 @@ class DomainIterator : public StackObj { if (bt == T_METADATA) { if (_depth == 0) { _first_field_pos = _i_domain_cc; + _null_markers = 0; } _depth++; } else if (bt == T_VOID && (prev_bt != T_LONG && prev_bt != T_DOUBLE)) { @@ -2566,6 +2567,7 @@ void ConnectionGraph::process_call_arguments(CallNode *call) { PointsToNode* arg_ptn = ptnode_adr(arg->_idx); if (at->isa_ptr() != nullptr && call_analyzer->is_arg_returned(k) ) { + // The call returns arguments. if (meth->is_scalarized_arg(k)) { ProjNode* res_proj = call->proj_out_or_null(di.i_domain_cc() - di.first_field_pos() - di.null_markers() + TypeFunc::Parms + 1); @@ -2576,7 +2578,11 @@ void ConnectionGraph::process_call_arguments(CallNode *call) { // assert(arg_ptn != nullptr, "node should be registered"); // add_edge(call_ptn, arg_ptn); } else { - add_edge(ptnode_adr(res_proj->_idx), arg_ptn); + PointsToNode* proj_ptn = ptnode_adr(res_proj->_idx); + add_edge(proj_ptn, arg_ptn); + if (!call_analyzer->is_return_local()) { + add_edge(proj_ptn, phantom_obj); + } } } } else if (call_ptn != nullptr) { // Is call's result used? diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestOptimizePtrCompare.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestOptimizePtrCompare.java index 1d2191a5f88..13808e8c8d5 100644 --- a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestOptimizePtrCompare.java +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestOptimizePtrCompare.java @@ -84,6 +84,7 @@ public static void test2() { static MyValue notInlined2(MyValue v) { return v; } + static value class MyValue2 { Object o1; Object o2; @@ -94,6 +95,16 @@ static value class MyValue2 { } } + static value class MyValue3 { + MyValue v1; + MyValue2 v2; + + MyValue3(MyValue v1, MyValue2 v2) { + this.v1 = v1; + this.v2 = v2; + } + } + static Object fieldO = new Object(); @Test @@ -152,4 +163,71 @@ public static void test5() { static MyValue2 notInlined5(MyValue2 v) { return v; } + + @Test + @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "true", "InlineTypeReturnedAsFields", "true"}, failOn = {IRNode.CMP_P_OR_N}) + // @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "false", "InlineTypeReturnedAsFields", "true"}, counts = {IRNode.CMP_P_OR_N, "2", IRNode.CMP_I, "1"}) + // @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "true", "InlineTypeReturnedAsFields", "false"}, counts = {IRNode.CMP_P_OR_N, "2"}) + // @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "false", "InlineTypeReturnedAsFields", "false"}, counts = {IRNode.CMP_P_OR_N, "3"}) + public static void test6() { + Object notUsed = new Object(); // make sure EA runs + MyValue v1 = new MyValue(fieldO); + MyValue2 v2 = new MyValue2(fieldO, null); + MyValue3 v3 = new MyValue3(v1, v2); + MyValue3 res = notInlined6(v1, v2, v3); + if (res.v2.o2 != null) { + throw new RuntimeException("never taken"); + } + } + + @DontInline + static MyValue3 notInlined6(MyValue v1, MyValue2 v2, MyValue3 v3) { + return v3; + } + + @Test + @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "true", "InlineTypeReturnedAsFields", "true"}, counts = {IRNode.CMP_P_OR_N, "1"}) + // @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "false", "InlineTypeReturnedAsFields", "true"}, counts = {IRNode.CMP_P_OR_N, "2", IRNode.CMP_I, "1"}) + // @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "true", "InlineTypeReturnedAsFields", "false"}, counts = {IRNode.CMP_P_OR_N, "2"}) + // @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "false", "InlineTypeReturnedAsFields", "false"}, counts = {IRNode.CMP_P_OR_N, "3"}) + public static void test7() { + Object notUsed = new Object(); // make sure EA runs + MyValue2 arg = new MyValue2(fieldO, null); + MyValue2 res = notInlined7(arg, true); + if (res.o2 != null) { + throw new RuntimeException("never taken"); + } + } + + @DontInline + static MyValue2 notInlined7(MyValue2 v, boolean flag) { + if (flag) { + return v; + } + return new MyValue2(fieldO, fieldO); + } + + @Test + @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "true", "InlineTypeReturnedAsFields", "true"}, counts = {IRNode.CMP_P_OR_N, "1"}) + // @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "false", "InlineTypeReturnedAsFields", "true"}, counts = {IRNode.CMP_P_OR_N, "2", IRNode.CMP_I, "1"}) + // @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "true", "InlineTypeReturnedAsFields", "false"}, counts = {IRNode.CMP_P_OR_N, "2"}) + // @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "false", "InlineTypeReturnedAsFields", "false"}, counts = {IRNode.CMP_P_OR_N, "3"}) + public static void test8() { + Object notUsed = new Object(); // make sure EA runs + MyValue2 arg = new MyValue2(fieldO, null); + MyValue2 res = notInlined8(arg, true); + if (res.o2 != null) { + throw new RuntimeException("never taken"); + } + } + + static MyValue2 fieldValue2 = new MyValue2(fieldO, fieldO); + + @DontInline + static MyValue2 notInlined8(MyValue2 v, boolean flag) { + if (flag) { + return v; + } + return fieldValue2; + } } From 7c7f03571394525ffd74bed798e737b93b4ebf8d Mon Sep 17 00:00:00 2001 From: rwestrel Date: Tue, 3 Mar 2026 16:28:01 +0100 Subject: [PATCH 08/11] more --- src/hotspot/share/opto/escape.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/hotspot/share/opto/escape.cpp b/src/hotspot/share/opto/escape.cpp index cb27f87533f..5e62ef5b52c 100644 --- a/src/hotspot/share/opto/escape.cpp +++ b/src/hotspot/share/opto/escape.cpp @@ -2565,9 +2565,13 @@ void ConnectionGraph::process_call_arguments(CallNode *call) { const Type* at = di.current_domain_cc(); Node* arg = call->in(di.i_domain_cc()); PointsToNode* arg_ptn = ptnode_adr(arg->_idx); + assert(!call_analyzer->is_arg_returned(k) || !meth->is_scalarized_arg(k) || + di.i_domain_cc() - di.first_field_pos() - di.null_markers() == 0 || + call->proj_out_or_null(di.i_domain_cc() - di.first_field_pos() - di.null_markers() + TypeFunc::Parms + 1) == nullptr || + _igvn->type(call->proj_out_or_null(di.i_domain_cc() - di.first_field_pos() - di.null_markers() + TypeFunc::Parms + 1)) == at, + ""); if (at->isa_ptr() != nullptr && call_analyzer->is_arg_returned(k) ) { - // The call returns arguments. if (meth->is_scalarized_arg(k)) { ProjNode* res_proj = call->proj_out_or_null(di.i_domain_cc() - di.first_field_pos() - di.null_markers() + TypeFunc::Parms + 1); From e0ad5df7a9411b37f0449a9be68cd6dff0887b03 Mon Sep 17 00:00:00 2001 From: rwestrel Date: Wed, 4 Mar 2026 13:28:45 +0100 Subject: [PATCH 09/11] more --- src/hotspot/share/opto/escape.cpp | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/src/hotspot/share/opto/escape.cpp b/src/hotspot/share/opto/escape.cpp index 5e62ef5b52c..ab3f6135649 100644 --- a/src/hotspot/share/opto/escape.cpp +++ b/src/hotspot/share/opto/escape.cpp @@ -1874,7 +1874,6 @@ class DomainIterator : public StackObj { uint _depth; uint _first_field_pos; const bool _is_static; - uint _null_markers; void next_helper() { if (_sig_cc == nullptr) { @@ -1887,7 +1886,6 @@ class DomainIterator : public StackObj { if (bt == T_METADATA) { if (_depth == 0) { _first_field_pos = _i_domain_cc; - _null_markers = 0; } _depth++; } else if (bt == T_VOID && (prev_bt != T_LONG && prev_bt != T_DOUBLE)) { @@ -1895,9 +1893,12 @@ class DomainIterator : public StackObj { if (_depth == 0) { _i_domain++; } - } else if (bt == T_BOOLEAN && prev_bt == T_METADATA && (_is_static || _i_domain > 0)) { - _null_markers++; - return; + } else if (bt == T_BOOLEAN && prev_bt == T_METADATA && (_is_static || _i_domain > 0) && _sig_cc->at(_i_sig_cc)._offset == -1) { + assert(_sig_cc->at(_i_sig_cc)._null_marker, ""); + assert(_depth == 1, ""); + _i_domain_cc++; + _first_field_pos = _i_domain_cc; + // return; } else { return; } @@ -1917,8 +1918,7 @@ class DomainIterator : public StackObj { _i_sig_cc(0), _depth(0), _first_field_pos(0), - _is_static(call->method()->is_static()), - _null_markers(0) { + _is_static(call->method()->is_static()) { next_helper(); } @@ -1958,10 +1958,6 @@ class DomainIterator : public StackObj { assert(_first_field_pos >= TypeFunc::Parms, ""); return _first_field_pos; } - - uint null_markers() const { - return _null_markers; - } }; // Add final simple edges to graph. @@ -2566,15 +2562,15 @@ void ConnectionGraph::process_call_arguments(CallNode *call) { Node* arg = call->in(di.i_domain_cc()); PointsToNode* arg_ptn = ptnode_adr(arg->_idx); assert(!call_analyzer->is_arg_returned(k) || !meth->is_scalarized_arg(k) || - di.i_domain_cc() - di.first_field_pos() - di.null_markers() == 0 || - call->proj_out_or_null(di.i_domain_cc() - di.first_field_pos() - di.null_markers() + TypeFunc::Parms + 1) == nullptr || - _igvn->type(call->proj_out_or_null(di.i_domain_cc() - di.first_field_pos() - di.null_markers() + TypeFunc::Parms + 1)) == at, + di.i_domain_cc() <= di.first_field_pos() || + call->proj_out_or_null(di.i_domain_cc() - di.first_field_pos() + TypeFunc::Parms + 1) == nullptr || + _igvn->type(call->proj_out_or_null(di.i_domain_cc() - di.first_field_pos() + TypeFunc::Parms + 1)) == at, ""); if (at->isa_ptr() != nullptr && call_analyzer->is_arg_returned(k) ) { // The call returns arguments. if (meth->is_scalarized_arg(k)) { - ProjNode* res_proj = call->proj_out_or_null(di.i_domain_cc() - di.first_field_pos() - di.null_markers() + TypeFunc::Parms + 1); + ProjNode* res_proj = call->proj_out_or_null(di.i_domain_cc() - di.first_field_pos() + TypeFunc::Parms + 1); if (res_proj != nullptr) { assert(_igvn->type(res_proj)->isa_ptr(), ""); if (res_proj->_con == TypeFunc::Parms) { From 42744a17ef08ce2ee77016a77c9395ecbb9e901f Mon Sep 17 00:00:00 2001 From: rwestrel Date: Thu, 5 Mar 2026 15:51:44 +0100 Subject: [PATCH 10/11] more --- src/hotspot/share/opto/escape.cpp | 20 +++++++++++++------ src/hotspot/share/opto/escape.hpp | 2 ++ .../inlinetypes/TestOptimizePtrCompare.java | 18 ++++++++--------- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/hotspot/share/opto/escape.cpp b/src/hotspot/share/opto/escape.cpp index ab3f6135649..29d412b4ebf 100644 --- a/src/hotspot/share/opto/escape.cpp +++ b/src/hotspot/share/opto/escape.cpp @@ -1603,7 +1603,7 @@ void ConnectionGraph::add_proj(Node* n, Unique_Node_List* delayed_worklist) { for (uint i = TypeFunc::Parms; i < d->cnt(); i++) { if (d->field_at(i)->isa_ptr() != nullptr && call_analyzer->is_arg_returned(i - TypeFunc::Parms) && - meth->is_scalarized_arg(i - TypeFunc::Parms)) { + scalarized_arg_with_compatible_return(call->as_CallJava(), i)) { ret_arg = true; break; } @@ -2348,7 +2348,7 @@ void ConnectionGraph::add_call_node(CallNode* call) { for (uint i = TypeFunc::Parms; i < d->cnt(); i++) { if (d->field_at(i)->isa_ptr() != nullptr && call_analyzer->is_arg_returned(i - TypeFunc::Parms) && - !meth->is_scalarized_arg(i - TypeFunc::Parms)) { + !scalarized_arg_with_compatible_return(call->as_CallJava(), i)) { ret_arg = true; break; } @@ -2369,6 +2369,15 @@ void ConnectionGraph::add_call_node(CallNode* call) { } } +bool ConnectionGraph::scalarized_arg_with_compatible_return(CallJavaNode* call, uint k) { + ciMethod* meth = call->method(); + BCEscapeAnalyzer* call_analyzer = meth->get_bcea(); + assert(call_analyzer->is_arg_returned(k - TypeFunc::Parms), ""); + return meth->is_scalarized_arg(k - TypeFunc::Parms) && + call->tf()->domain_sig()->field_at(k)->meet(TypePtr::NULL_PTR)->higher_equal( + call->tf()->range_sig()->field_at(TypeFunc::Parms)); +} + void ConnectionGraph::process_call_arguments(CallNode *call) { bool is_arraycopy = false; switch (call->Opcode()) { @@ -2561,15 +2570,14 @@ void ConnectionGraph::process_call_arguments(CallNode *call) { const Type* at = di.current_domain_cc(); Node* arg = call->in(di.i_domain_cc()); PointsToNode* arg_ptn = ptnode_adr(arg->_idx); - assert(!call_analyzer->is_arg_returned(k) || !meth->is_scalarized_arg(k) || - di.i_domain_cc() <= di.first_field_pos() || + assert(!call_analyzer->is_arg_returned(k) || !scalarized_arg_with_compatible_return(call->as_CallJava(), di.i_domain()) || call->proj_out_or_null(di.i_domain_cc() - di.first_field_pos() + TypeFunc::Parms + 1) == nullptr || _igvn->type(call->proj_out_or_null(di.i_domain_cc() - di.first_field_pos() + TypeFunc::Parms + 1)) == at, ""); if (at->isa_ptr() != nullptr && - call_analyzer->is_arg_returned(k) ) { + call_analyzer->is_arg_returned(k)) { // The call returns arguments. - if (meth->is_scalarized_arg(k)) { + if (scalarized_arg_with_compatible_return(call->as_CallJava(), di.i_domain())) { ProjNode* res_proj = call->proj_out_or_null(di.i_domain_cc() - di.first_field_pos() + TypeFunc::Parms + 1); if (res_proj != nullptr) { assert(_igvn->type(res_proj)->isa_ptr(), ""); diff --git a/src/hotspot/share/opto/escape.hpp b/src/hotspot/share/opto/escape.hpp index c04d451331c..4921601ff7e 100644 --- a/src/hotspot/share/opto/escape.hpp +++ b/src/hotspot/share/opto/escape.hpp @@ -692,6 +692,8 @@ class ConnectionGraph: public ArenaObj { void add_to_congraph_unsafe_access(Node* n, uint opcode, Unique_Node_List* delayed_worklist); bool add_final_edges_unsafe_access(Node* n, uint opcode); + bool scalarized_arg_with_compatible_return(CallJavaNode* call, uint k); + #ifndef PRODUCT static int _no_escape_counter; static int _arg_escape_counter; diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestOptimizePtrCompare.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestOptimizePtrCompare.java index 13808e8c8d5..8aea4d6da25 100644 --- a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestOptimizePtrCompare.java +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestOptimizePtrCompare.java @@ -166,9 +166,9 @@ static MyValue2 notInlined5(MyValue2 v) { @Test @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "true", "InlineTypeReturnedAsFields", "true"}, failOn = {IRNode.CMP_P_OR_N}) - // @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "false", "InlineTypeReturnedAsFields", "true"}, counts = {IRNode.CMP_P_OR_N, "2", IRNode.CMP_I, "1"}) - // @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "true", "InlineTypeReturnedAsFields", "false"}, counts = {IRNode.CMP_P_OR_N, "2"}) - // @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "false", "InlineTypeReturnedAsFields", "false"}, counts = {IRNode.CMP_P_OR_N, "3"}) + @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "false", "InlineTypeReturnedAsFields", "true"}, counts = {IRNode.CMP_P_OR_N, "4", IRNode.CMP_I, "2"}) + @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "true", "InlineTypeReturnedAsFields", "false"}, counts = {IRNode.CMP_P_OR_N, "2"}) + @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "false", "InlineTypeReturnedAsFields", "false"}, counts = {IRNode.CMP_P_OR_N, "5"}) public static void test6() { Object notUsed = new Object(); // make sure EA runs MyValue v1 = new MyValue(fieldO); @@ -187,9 +187,9 @@ static MyValue3 notInlined6(MyValue v1, MyValue2 v2, MyValue3 v3) { @Test @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "true", "InlineTypeReturnedAsFields", "true"}, counts = {IRNode.CMP_P_OR_N, "1"}) - // @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "false", "InlineTypeReturnedAsFields", "true"}, counts = {IRNode.CMP_P_OR_N, "2", IRNode.CMP_I, "1"}) - // @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "true", "InlineTypeReturnedAsFields", "false"}, counts = {IRNode.CMP_P_OR_N, "2"}) - // @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "false", "InlineTypeReturnedAsFields", "false"}, counts = {IRNode.CMP_P_OR_N, "3"}) + @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "false", "InlineTypeReturnedAsFields", "true"}, counts = {IRNode.CMP_P_OR_N, "2", IRNode.CMP_I, "1"}) + @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "true", "InlineTypeReturnedAsFields", "false"}, counts = {IRNode.CMP_P_OR_N, "2"}) + @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "false", "InlineTypeReturnedAsFields", "false"}, counts = {IRNode.CMP_P_OR_N, "3"}) public static void test7() { Object notUsed = new Object(); // make sure EA runs MyValue2 arg = new MyValue2(fieldO, null); @@ -209,9 +209,9 @@ static MyValue2 notInlined7(MyValue2 v, boolean flag) { @Test @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "true", "InlineTypeReturnedAsFields", "true"}, counts = {IRNode.CMP_P_OR_N, "1"}) - // @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "false", "InlineTypeReturnedAsFields", "true"}, counts = {IRNode.CMP_P_OR_N, "2", IRNode.CMP_I, "1"}) - // @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "true", "InlineTypeReturnedAsFields", "false"}, counts = {IRNode.CMP_P_OR_N, "2"}) - // @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "false", "InlineTypeReturnedAsFields", "false"}, counts = {IRNode.CMP_P_OR_N, "3"}) + @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "false", "InlineTypeReturnedAsFields", "true"}, counts = {IRNode.CMP_P_OR_N, "2", IRNode.CMP_I, "1"}) + @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "true", "InlineTypeReturnedAsFields", "false"}, counts = {IRNode.CMP_P_OR_N, "2"}) + @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "false", "InlineTypeReturnedAsFields", "false"}, counts = {IRNode.CMP_P_OR_N, "3"}) public static void test8() { Object notUsed = new Object(); // make sure EA runs MyValue2 arg = new MyValue2(fieldO, null); From d25fca1158dd9667f5821a19196aae4be4670960 Mon Sep 17 00:00:00 2001 From: rwestrel Date: Thu, 5 Mar 2026 16:18:42 +0100 Subject: [PATCH 11/11] test fix --- .../compiler/valhalla/inlinetypes/TestOptimizePtrCompare.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestOptimizePtrCompare.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestOptimizePtrCompare.java index 8aea4d6da25..9962631c4af 100644 --- a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestOptimizePtrCompare.java +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestOptimizePtrCompare.java @@ -164,7 +164,7 @@ static MyValue2 notInlined5(MyValue2 v) { return v; } - @Test + @Test(allowNotCompilable = true) @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "true", "InlineTypeReturnedAsFields", "true"}, failOn = {IRNode.CMP_P_OR_N}) @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "false", "InlineTypeReturnedAsFields", "true"}, counts = {IRNode.CMP_P_OR_N, "4", IRNode.CMP_I, "2"}) @IR(applyIfAnd = {"InlineTypePassFieldsAsArgs", "true", "InlineTypeReturnedAsFields", "false"}, counts = {IRNode.CMP_P_OR_N, "2"})