8280469: C2: CHA support for interface calls when inlining through method handle linker

Reviewed-by: kvn, roland
This commit is contained in:
Vladimir Ivanov 2025-11-18 22:29:37 +00:00
parent b086e34f71
commit 256a9beffc
4 changed files with 204 additions and 32 deletions

View File

@ -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()) {

View File

@ -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);
}

View File

@ -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.

View File

@ -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<ObjectHashCode.I> {
@ -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<TwoLevelHierarchyLinear.I> {
@ -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<ThreeLevelHierarchyLinear.I> {
@ -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<ThreeLevelHierarchyAbstractVsDefault.I> {
@ -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<ThreeLevelDefaultHierarchy.I> {
@ -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<ThreeLevelDefaultHierarchy1.I> {
@ -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()
}
}
}
}