diff --git a/src/hotspot/share/opto/callnode.cpp b/src/hotspot/share/opto/callnode.cpp index 0bd9918083f..140ecfe5810 100644 --- a/src/hotspot/share/opto/callnode.cpp +++ b/src/hotspot/share/opto/callnode.cpp @@ -1403,7 +1403,7 @@ bool CallStaticJavaNode::remove_unknown_flat_array_load(PhaseIterGVN* igvn, Node Node* CallStaticJavaNode::replace_is_substitutable(PhaseIterGVN* igvn) { Node* left = in(TypeFunc::Parms); Node* right = in(TypeFunc::Parms + 1); - if (!InlineTypeNode::can_emit_substitutability_check(left, right)) { + if (!InlineTypeNode::can_emit_substitutability_check(igvn, left, right)) { return nullptr; } diff --git a/src/hotspot/share/opto/inlinetypenode.cpp b/src/hotspot/share/opto/inlinetypenode.cpp index 216ccd1361a..2eb3d921807 100644 --- a/src/hotspot/share/opto/inlinetypenode.cpp +++ b/src/hotspot/share/opto/inlinetypenode.cpp @@ -598,14 +598,32 @@ static bool check_cycle(ciInlineKlass* vk) { return false; } +// Check if 'lhs' and 'rhs' are the same oop, possibly wrapped in an InlineTypeNode. +static bool same_oop(PhaseGVN* phase, Node* lhs, Node* rhs) { + InlineTypeNode* lhs_inline = lhs->isa_InlineType(); + if (lhs_inline != nullptr && lhs_inline->is_allocated(phase)) { + lhs = lhs_inline->get_oop(); + } + InlineTypeNode* rhs_inline = rhs->isa_InlineType(); + if (rhs_inline != nullptr && rhs_inline->is_allocated(phase)) { + rhs = rhs_inline->get_oop(); + } + return lhs->eqv_uncast(rhs); +} + // Check if a substitutability check between 'lhs' and 'rhs' can be implemented in IR -bool InlineTypeNode::can_emit_substitutability_check(Node* lhs, Node* rhs) { +bool InlineTypeNode::can_emit_substitutability_check(PhaseGVN* phase, Node* lhs, Node* rhs) { + // We can't create new InlineTypeNodes after macro expansion + if (!phase->C->allow_macro_nodes()) { + return false; + } + if (!lhs->bottom_type()->isa_ptr() || (rhs != nullptr && !rhs->bottom_type()->isa_ptr())) { return false; } - if (rhs != nullptr && lhs->eqv_uncast(rhs)) { + if (rhs != nullptr && same_oop(phase, lhs, rhs)) { return true; } @@ -641,7 +659,7 @@ bool InlineTypeNode::can_emit_substitutability_check(Node* lhs, Node* rhs) { Node* lhs_fv = lhs_inline->field_value(i); Node* rhs_fv = rhs_inline != nullptr ? rhs_inline->field_value(i) : nullptr; - if (!can_emit_substitutability_check(lhs_fv, rhs_fv)) { + if (!can_emit_substitutability_check(phase, lhs_fv, rhs_fv)) { return false; } } @@ -696,7 +714,7 @@ static Node* emit_substitutability_check_pointer(GraphKit* kit, PhiNode* result, } Node* cmp = nullptr; - if (lhs->eqv_uncast(rhs)) { + if (same_oop(&gvn, lhs, rhs)) { cmp = kit->intcon(0); } else if (!lhs_type->is_ptr()->can_be_inline_type() || !rhs_type->is_ptr()->can_be_inline_type()) { // If one of the sides is not a value object, can only be substitutable if they are the same diff --git a/src/hotspot/share/opto/inlinetypenode.hpp b/src/hotspot/share/opto/inlinetypenode.hpp index af19c5a5535..37197680f3d 100644 --- a/src/hotspot/share/opto/inlinetypenode.hpp +++ b/src/hotspot/share/opto/inlinetypenode.hpp @@ -132,7 +132,7 @@ public: void store_flat_array(GraphKit* kit, Node* base, Node* idx); // Implementation of the substitutability check for acmp - static bool can_emit_substitutability_check(Node* lhs, Node* rhs); + static bool can_emit_substitutability_check(PhaseGVN* phase, Node* lhs, Node* rhs); static Node* emit_substitutability_check(GraphKit* kit, Node* lhs, Node* rhs); // Allocates the inline type (if not yet allocated) diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestSubstitutabilityExpansionAfterMacro.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestSubstitutabilityExpansionAfterMacro.java new file mode 100644 index 00000000000..56ddca2741b --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestSubstitutabilityExpansionAfterMacro.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8388441 + * @summary Test acmp optimization when operands become known after macro expansion + * @requires vm.compiler2.enabled + * @library /test/lib + * @enablePreview + * @run main/othervm -XX:-TieredCompilation -Xbatch ${test.main.class} + */ + +import jdk.test.lib.Asserts; + +public class TestSubstitutabilityExpansionAfterMacro { + record Box(Object value) { } + value record MyValue(Object value) { } + + static boolean equals(Object a, Object b) { + return a == b; + } + + // Below tests trigger InlineTypeNode::can_emit_substitutability_check only + // after macro expansion. + + // EA leaves the right operand as Phi(InlineType, LoadN). + // The cast creates a buffered InlineTypeNode whose oop is a cast of that phi. + // After InlineTypeNode removal after macro expansion, both operands are equivalent. + static boolean test1(boolean b) { + Box box = b ? new Box(new MyValue(null)) : new Box(null); + if (box.value == null) { + box = new Box(new MyValue(null)); + } + return equals((MyValue) box.value, box.value); + } + + // InlineTypeNode removal changes Phi(InlineType(oop=null), exact Object) + // to Phi(null, exact Object) as the right operand. The phi then becomes an exact + // nullable Object and can_be_inline_type() changes to false after macro expansion. + static boolean test2(Object obj, boolean b) { + return equals(obj, b ? (Integer) null : new Object()); + } + + // Both operands are phis of an InlineTypeNode and its oop. After InlineTypeNode + // removal, both phis collapse to the same oop and the operands become equivalent + // after macro expansion. + static boolean test3(Object obj, boolean b) { + Object left = b ? (MyValue) obj : obj; + Object right = b ? obj : (MyValue) obj; + // Use local acmp for fresh profiling + return left == right; + } + + public static void main(String[] args) { + // Warmup and profile acmp with identity operands + for (int i = 0; i < 10_000; i++) { + equals(args, args); + } + Object obj = new Object(); + MyValue val = new MyValue(obj); + for (int i = 0; i < 50_000; i++) { + boolean b = (i & 1) == 0; + Asserts.assertEQ(test1(b), true); + Asserts.assertEQ(test2(b ? null : obj, b), b); + Asserts.assertEQ(test3(val, b), true); + } + } +} +