diff --git a/src/hotspot/share/ci/ciInstanceKlass.cpp b/src/hotspot/share/ci/ciInstanceKlass.cpp index 9bbf005356c..64b9acf9146 100644 --- a/src/hotspot/share/ci/ciInstanceKlass.cpp +++ b/src/hotspot/share/ci/ciInstanceKlass.cpp @@ -605,7 +605,7 @@ bool ciInstanceKlass::is_leaf_type() { if (is_shared()) { return is_final(); // approximately correct } else { - return !has_subklass() && (nof_implementors() == 0); + return !has_subklass() && (!is_interface() || nof_implementors() == 0); } } @@ -619,6 +619,7 @@ bool ciInstanceKlass::is_leaf_type() { // This is OK, since any dependencies we decide to assert // will be checked later under the Compile_lock. ciInstanceKlass* ciInstanceKlass::implementor() { + assert(is_interface(), "required"); ciInstanceKlass* impl = _implementor; if (impl == nullptr) { if (is_shared()) { diff --git a/src/hotspot/share/ci/ciInstanceKlass.hpp b/src/hotspot/share/ci/ciInstanceKlass.hpp index ec8fc789c7d..1f887771f54 100644 --- a/src/hotspot/share/ci/ciInstanceKlass.hpp +++ b/src/hotspot/share/ci/ciInstanceKlass.hpp @@ -259,6 +259,7 @@ public: ciInstanceKlass* unique_implementor() { assert(is_loaded(), "must be loaded"); + assert(is_interface(), "must be"); ciInstanceKlass* impl = implementor(); return (impl != this ? impl : nullptr); } diff --git a/src/hotspot/share/opto/doCall.cpp b/src/hotspot/share/opto/doCall.cpp index 754b0fa8d1c..91bb743618b 100644 --- a/src/hotspot/share/opto/doCall.cpp +++ b/src/hotspot/share/opto/doCall.cpp @@ -97,10 +97,9 @@ CallGenerator* Compile::call_generator(ciMethod* callee, int vtable_index, bool Bytecodes::Code bytecode = caller->java_code_at_bci(bci); ciMethod* orig_callee = caller->get_method_at_bci(bci); - const bool is_virtual_or_interface = (bytecode == Bytecodes::_invokevirtual) || - (bytecode == Bytecodes::_invokeinterface) || - (orig_callee->intrinsic_id() == vmIntrinsics::_linkToVirtual) || - (orig_callee->intrinsic_id() == vmIntrinsics::_linkToInterface); + const bool is_virtual = (bytecode == Bytecodes::_invokevirtual) || (orig_callee->intrinsic_id() == vmIntrinsics::_linkToVirtual); + const bool is_interface = (bytecode == Bytecodes::_invokeinterface) || (orig_callee->intrinsic_id() == vmIntrinsics::_linkToInterface); + const bool is_virtual_or_interface = is_virtual || is_interface; const bool check_access = !orig_callee->is_method_handle_intrinsic(); // method handle intrinsics don't perform access checks @@ -339,17 +338,25 @@ CallGenerator* Compile::call_generator(ciMethod* callee, int vtable_index, bool // number of implementors for decl_interface is 0 or 1. If // it's 0 then no class implements decl_interface and there's // no point in inlining. - if (call_does_dispatch && bytecode == Bytecodes::_invokeinterface) { - ciInstanceKlass* declared_interface = - caller->get_declared_method_holder_at_bci(bci)->as_instance_klass(); + if (call_does_dispatch && is_interface) { + ciInstanceKlass* declared_interface = nullptr; + if (orig_callee->intrinsic_id() == vmIntrinsics::_linkToInterface) { + // MemberName doesn't keep information about resolved interface class (REFC) once + // resolution is over, but resolved method holder (DECC) can be used as a + // conservative approximation. + declared_interface = callee->holder(); + } else { + assert(!orig_callee->is_method_handle_intrinsic(), "not allowed"); + declared_interface = caller->get_declared_method_holder_at_bci(bci)->as_instance_klass(); + } + assert(declared_interface->is_interface(), "required"); ciInstanceKlass* singleton = declared_interface->unique_implementor(); if (singleton != nullptr) { assert(singleton != declared_interface, "not a unique implementor"); - assert(check_access, "required"); ciMethod* cha_monomorphic_target = - callee->find_monomorphic_target(caller->holder(), declared_interface, singleton); + callee->find_monomorphic_target(caller->holder(), declared_interface, singleton, check_access); if (cha_monomorphic_target != nullptr && cha_monomorphic_target->holder() != env()->Object_klass()) { // subtype check against Object is useless @@ -372,7 +379,7 @@ CallGenerator* Compile::call_generator(ciMethod* callee, int vtable_index, bool } } } - } // call_does_dispatch && bytecode == Bytecodes::_invokeinterface + } // call_does_dispatch && is_interface // Nothing claimed the intrinsic, we go with straight-forward inlining // for already discovered intrinsic. diff --git a/test/hotspot/jtreg/compiler/cha/StrengthReduceInterfaceCall.java b/test/hotspot/jtreg/compiler/cha/StrengthReduceInterfaceCall.java index aa014cfa63d..5e2dc2e3d56 100644 --- a/test/hotspot/jtreg/compiler/cha/StrengthReduceInterfaceCall.java +++ b/test/hotspot/jtreg/compiler/cha/StrengthReduceInterfaceCall.java @@ -24,6 +24,7 @@ /* * @test * @requires !vm.graal.enabled + * @requires vm.opt.StressMethodHandleLinkerInlining == null | !vm.opt.StressMethodHandleLinkerInlining * @requires vm.opt.StressUnstableIfTraps == null | !vm.opt.StressUnstableIfTraps * @modules java.base/jdk.internal.misc * java.base/jdk.internal.vm.annotation @@ -55,6 +56,9 @@ package compiler.cha; import jdk.internal.vm.annotation.DontInline; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; + import static compiler.cha.Utils.*; public class StrengthReduceInterfaceCall { @@ -66,6 +70,18 @@ public class StrengthReduceInterfaceCall { run(ThreeLevelHierarchyAbstractVsDefault.class); run(ThreeLevelDefaultHierarchy.class); run(ThreeLevelDefaultHierarchy1.class); + + // Implementation limitation: CHA is not performed by C1 during inlining through MH linkers. + if (!jdk.test.whitebox.code.Compiler.isC1Enabled()) { + run(ObjectToString.TestMH.class, ObjectToString.class); + run(ObjectHashCode.TestMH.class, ObjectHashCode.class); + run(TwoLevelHierarchyLinear.TestMH.class, TwoLevelHierarchyLinear.class); + run(ThreeLevelHierarchyLinear.TestMH.class, ThreeLevelHierarchyLinear.class); + run(ThreeLevelHierarchyAbstractVsDefault.TestMH.class, ThreeLevelHierarchyAbstractVsDefault.class); + run(ThreeLevelDefaultHierarchy.TestMH.class, ThreeLevelDefaultHierarchy.class); + run(ThreeLevelDefaultHierarchy1.TestMH.class, ThreeLevelDefaultHierarchy1.class); + } + System.out.println("TEST PASSED"); } @@ -87,7 +103,7 @@ public class StrengthReduceInterfaceCall { static class DJ2 implements J { public String toString() { return "DJ2"; }} @Override - public Object test(I i) { return ObjectToStringHelper.testHelper(i); /* invokeinterface I.toString() */ } + public Object test(I i) throws Throwable { return ObjectToStringHelper.testHelper(i); /* invokeinterface I.toString() */ } @TestCase public void testMono() { @@ -155,6 +171,20 @@ public class StrengthReduceInterfaceCall { }); assertCompiled(); } + + public static class TestMH extends ObjectToString { + static final MethodHandle TEST_MH = findVirtualHelper(I.class, "toString", String.class, MethodHandles.lookup()); + + @Override + public void checkInvalidReceiver() { + // receiver type check failures trigger nmethod invalidation + } + + @Override + public Object test(I obj) throws Throwable { + return (String)TEST_MH.invokeExact(obj); // invokeinterface I.toString() + } + } } public static class ObjectHashCode extends ATest { @@ -175,7 +205,7 @@ public class StrengthReduceInterfaceCall { static class DJ2 implements J { public int hashCode() { return super.hashCode(); }} @Override - public Object test(I i) { + public Object test(I i) throws Throwable { return ObjectHashCodeHelper.testHelper(i); /* invokeinterface I.hashCode() */ } @@ -242,6 +272,20 @@ public class StrengthReduceInterfaceCall { }); assertCompiled(); } + + public static class TestMH extends ObjectHashCode { + static final MethodHandle TEST_MH = findVirtualHelper(I.class, "hashCode", int.class, MethodHandles.lookup()); + + @Override + public void checkInvalidReceiver() { + // receiver type check failures trigger nmethod invalidation + } + + @Override + public Object test(I obj) throws Throwable { + return (int)TEST_MH.invokeExact(obj); // invokeinterface I.hashCode() + } + } } public static class TwoLevelHierarchyLinear extends ATest { @@ -263,7 +307,7 @@ public class StrengthReduceInterfaceCall { static class DJ2 implements J { public Object m() { return WRONG; }} @DontInline - public Object test(I i) { + public Object test(I i) throws Throwable { return i.m(); } @@ -366,6 +410,20 @@ public class StrengthReduceInterfaceCall { }); assertCompiled(); } + + public static class TestMH extends TwoLevelHierarchyLinear { + static final MethodHandle TEST_MH = findVirtualHelper(I.class, "m", Object.class, MethodHandles.lookup()); + + @Override + public void checkInvalidReceiver() { + // receiver type check failures trigger nmethod invalidation + } + + @Override + public Object test(I obj) throws Throwable { + return TEST_MH.invokeExact(obj); // invokeinterface I.m() + } + } } public static class ThreeLevelHierarchyLinear extends ATest { @@ -385,7 +443,7 @@ public class StrengthReduceInterfaceCall { static class DJ implements J { public Object m() { return WRONG; }} @DontInline - public Object test(I i) { + public Object test(I i) throws Throwable { return i.m(); // I <: J.m ABSTRACT } @@ -404,10 +462,16 @@ public class StrengthReduceInterfaceCall { assertCompiled(); // No deopt on not-yet-seen receiver // 2. No dependency invalidation: different context - initialize(DJ.class, // DJ.m <: intf J.m ABSTRACT - K1.class, // intf K1 <: intf I <: intf J.m ABSTRACT - K2.class); // intf K2.m ABSTRACT <: intf I <: intf J.m ABSTRACT - assertCompiled(); + if (contextClass() == I.class) { + initialize(DJ.class, // DJ.m <: intf J.m ABSTRACT + K1.class, // intf K1 <: intf I <: intf J.m ABSTRACT + K2.class); // intf K2.m ABSTRACT <: intf I <: intf J.m ABSTRACT + assertCompiled(); + } else if (contextClass() == J.class) { + // no classes to initialize w/o breaking a dependency + } else { + throw new InternalError("unsupported context: " + contextClass()); + } // 3. Dependency invalidation: DI.m <: I initialize(DI.class); // DI.m <: intf I <: intf J.m ABSTRACT @@ -491,6 +555,30 @@ public class StrengthReduceInterfaceCall { }); assertCompiled(); } + + Class contextClass() { + return I.class; + } + + public static class TestMH extends ThreeLevelHierarchyLinear { + static final MethodHandle TEST_MH = findVirtualHelper(I.class, "m", Object.class, MethodHandles.lookup()); + + @Override + public void checkInvalidReceiver() { + // receiver type check failures trigger nmethod invalidation + } + + @Override + Class contextClass() { + return J.class; + } + + @Override + public Object test(I obj) throws Throwable { + return TEST_MH.invokeExact(obj); // invokeinterface I.m() + } + } + } public static class ThreeLevelHierarchyAbstractVsDefault extends ATest { @@ -503,7 +591,7 @@ public class StrengthReduceInterfaceCall { static class C implements I { public Object m() { return CORRECT; }} @DontInline - public Object test(I i) { + public Object test(I i) throws Throwable { return i.m(); // intf I.m OVERPASS } @@ -598,6 +686,20 @@ public class StrengthReduceInterfaceCall { }); assertCompiled(); } + + public static class TestMH extends ThreeLevelHierarchyAbstractVsDefault { + static final MethodHandle TEST_MH = findVirtualHelper(I.class, "m", Object.class, MethodHandles.lookup()); + + @Override + public void checkInvalidReceiver() { + // receiver type check failures trigger nmethod invalidation + } + + @Override + public Object test(I obj) throws Throwable { + return TEST_MH.invokeExact(obj); // invokeinterface I.m() + } + } } public static class ThreeLevelDefaultHierarchy extends ATest { @@ -617,7 +719,7 @@ public class StrengthReduceInterfaceCall { static class DK3 implements K3 {} @DontInline - public Object test(I i) { + public Object test(I i) throws Throwable { return i.m(); } @@ -636,11 +738,17 @@ public class StrengthReduceInterfaceCall { assertCompiled(); // 2. No dependency invalidation - initialize(DJ.class, // DJ.m <: intf J.m ABSTRACT - K1.class, // intf K1 <: intf I <: intf J.m ABSTRACT - K2.class, // intf K2.m ABSTRACT <: intf I <: intf J.m ABSTRACT - DK3.class); // DK3.m <: intf K3.m DEFAULT <: intf J.m ABSTRACT - assertCompiled(); + if (contextClass() == I.class) { + initialize(DJ.class, // DJ.m <: intf J.m ABSTRACT + K1.class, // intf K1 <: intf I <: intf J.m ABSTRACT + K2.class, // intf K2.m ABSTRACT <: intf I <: intf J.m ABSTRACT + DK3.class); // DK3.m <: intf K3.m DEFAULT <: intf J.m ABSTRACT + assertCompiled(); + } else if (contextClass() == J.class) { + // no classes to initialize w/o breaking a dependency + } else { + throw new InternalError("unsupported context: " + contextClass()); + } // 3. Dependency invalidation initialize(DI.class); // DI.m <: intf I <: intf J.m ABSTRACT @@ -666,6 +774,29 @@ public class StrengthReduceInterfaceCall { }); assertCompiled(); } + + Class contextClass() { + return I.class; + } + + public static class TestMH extends ThreeLevelDefaultHierarchy { + static final MethodHandle TEST_MH = findVirtualHelper(I.class, "m", Object.class, MethodHandles.lookup()); + + @Override + public void checkInvalidReceiver() { + // receiver type check failures trigger nmethod invalidation + } + + @Override + Class contextClass() { + return J.class; + } + + @Override + public Object test(I obj) throws Throwable { + return TEST_MH.invokeExact(obj); // invokeinterface I.m() + } + } } public static class ThreeLevelDefaultHierarchy1 extends ATest { @@ -686,7 +817,7 @@ public class StrengthReduceInterfaceCall { static class DJ2 implements J2 { public Object m() { return WRONG; }} @DontInline - public Object test(I i) { + public Object test(I i) throws Throwable { return i.m(); } @@ -705,11 +836,20 @@ public class StrengthReduceInterfaceCall { assertCompiled(); // 2. No dependency invalidation - initialize(DJ1.class, - DJ2.class, - K1.class, - K2.class, - K3.class); + if (contextClass() == I.class) { + initialize(DJ1.class, // DJ1.m <: intf J1 + DJ2.class, // DJ2.m <: intf J2.m ABSTRACT + K1.class, // intf K1 <: intf I <: intf J1, intf J2.m ABSTRACT + K2.class, // intf K2.m ABSTRACT <: intf I <: intf J1, intf J2.m ABSTRACT + K3.class); // intf K3.m DEFAULT <: intf I <: intf J1, intf J2.m ABSTRACT + } else if (contextClass() == J2.class) { + initialize(DJ1.class, // DJ1.m <: intf J1 + K1.class, // intf K1 <: intf I <: intf J1, intf J2.m ABSTRACT + K2.class, // intf K2.m ABSTRACT <: intf I <: intf J1, intf J2.m ABSTRACT + K3.class); // intf K3.m DEFAULT <: intf I <: intf J1, intf J2.m ABSTRACT + } else { + throw new InternalError("unsupported context: " + contextClass()); + } assertCompiled(); // 3. Dependency invalidation @@ -742,5 +882,28 @@ public class StrengthReduceInterfaceCall { }); assertCompiled(); } + + Class contextClass() { + return I.class; + } + + public static class TestMH extends ThreeLevelDefaultHierarchy1 { + static final MethodHandle TEST_MH = findVirtualHelper(I.class, "m", Object.class, MethodHandles.lookup()); + + @Override + public void checkInvalidReceiver() { + // receiver type check failures trigger nmethod invalidation + } + + @Override + Class contextClass() { + return J2.class; + } + + @Override + public Object test(I obj) throws Throwable { + return TEST_MH.invokeExact(obj); // invokeinterface I.m() + } + } } }