diff --git a/src/hotspot/share/cds/aotClassInitializer.cpp b/src/hotspot/share/cds/aotClassInitializer.cpp index be7e1f31d8a52..6f45c979880ca 100644 --- a/src/hotspot/share/cds/aotClassInitializer.cpp +++ b/src/hotspot/share/cds/aotClassInitializer.cpp @@ -321,6 +321,10 @@ bool AOTClassInitializer::can_archive_initialized_mirror(InstanceKlass* ik) { if (is_allowed(indy_specs, ik)) { return true; } + + if (ik->name()->starts_with("java/lang/invoke/MethodHandleImpl")) { + return true; + } } #ifdef ASSERT @@ -339,6 +343,7 @@ bool AOTClassInitializer::is_runtime_setup_required(InstanceKlass* ik) { return ik == vmClasses::Class_klass() || ik == vmClasses::internal_Unsafe_klass() || ik == vmClasses::ConcurrentHashMap_klass() || + ik == vmClasses::MethodHandleImpl_klass() || ik == vmClasses::Reference_klass(); } diff --git a/src/hotspot/share/cds/aotConstantPoolResolver.cpp b/src/hotspot/share/cds/aotConstantPoolResolver.cpp index 15ca2b2c2a0d7..b34650adf92b5 100644 --- a/src/hotspot/share/cds/aotConstantPoolResolver.cpp +++ b/src/hotspot/share/cds/aotConstantPoolResolver.cpp @@ -525,7 +525,7 @@ bool AOTConstantPoolResolver::is_indy_resolution_deterministic(ConstantPool* cp, Symbol* factory_type_sig = cp->uncached_signature_ref_at(cp_index); if (log_is_enabled(Debug, cds, resolve)) { ResourceMark rm; - log_debug(cds, resolve)("Checking indy callsite signature [%d]: %s", cp_index, factory_type_sig->as_C_string()); + log_debug(cds, resolve)("Checking lambda callsite signature [%d]: %s", cp_index, factory_type_sig->as_C_string()); } if (!check_lambda_metafactory_signature(cp, factory_type_sig)) { diff --git a/src/hotspot/share/cds/cdsHeapVerifier.cpp b/src/hotspot/share/cds/cdsHeapVerifier.cpp index 9bab62dabe665..2e96f818c141c 100644 --- a/src/hotspot/share/cds/cdsHeapVerifier.cpp +++ b/src/hotspot/share/cds/cdsHeapVerifier.cpp @@ -138,11 +138,10 @@ CDSHeapVerifier::CDSHeapVerifier() : _archived_objs(0), _problems(0) "CD_Object_array", // E same as <...>ConstantUtils.CD_Object_array::CD_Object "INVOKER_SUPER_DESC"); // E same as java.lang.constant.ConstantDescs::CD_Object - ADD_EXCL("java/lang/invoke/MethodHandleImpl$ArrayAccessor", - "OBJECT_ARRAY_GETTER", // D - "OBJECT_ARRAY_SETTER", // D - "OBJECT_ARRAY_LENGTH"); // D - + ADD_EXCL("java/lang/runtime/ObjectMethods", "CLASS_IS_INSTANCE", // D + "FALSE", // D + "TRUE", // D + "ZERO"); // D } # undef ADD_EXCL diff --git a/src/hotspot/share/classfile/vmClassMacros.hpp b/src/hotspot/share/classfile/vmClassMacros.hpp index 351b9b0b53f24..1edc13054d474 100644 --- a/src/hotspot/share/classfile/vmClassMacros.hpp +++ b/src/hotspot/share/classfile/vmClassMacros.hpp @@ -114,6 +114,7 @@ do_klass(VarHandle_klass, java_lang_invoke_VarHandle ) \ do_klass(MemberName_klass, java_lang_invoke_MemberName ) \ do_klass(ResolvedMethodName_klass, java_lang_invoke_ResolvedMethodName ) \ + do_klass(MethodHandleImpl_klass, java_lang_invoke_MethodHandleImpl ) \ do_klass(MethodHandleNatives_klass, java_lang_invoke_MethodHandleNatives ) \ do_klass(LambdaForm_klass, java_lang_invoke_LambdaForm ) \ do_klass(MethodType_klass, java_lang_invoke_MethodType ) \ diff --git a/src/hotspot/share/oops/instanceKlass.cpp b/src/hotspot/share/oops/instanceKlass.cpp index 32445759491b3..3c48311b8717f 100644 --- a/src/hotspot/share/oops/instanceKlass.cpp +++ b/src/hotspot/share/oops/instanceKlass.cpp @@ -860,6 +860,12 @@ void InstanceKlass::initialize_with_aot_initialized_mirror(TRAPS) { return; } + if (is_runtime_setup_required()) { + // Need to take the slow path, which will call the runtimeSetup() function instead + // of + initialize(CHECK); + return; + } if (log_is_enabled(Info, cds, init)) { ResourceMark rm; log_info(cds, init)("%s (aot-inited)", external_name()); @@ -878,7 +884,6 @@ void InstanceKlass::initialize_with_aot_initialized_mirror(TRAPS) { #endif set_init_thread(THREAD); - AOTClassInitializer::call_runtime_setup(THREAD, this); set_initialization_state_and_notify(fully_initialized, CHECK); } #endif diff --git a/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java b/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java index cd97413182b19..d86226d5ee6f6 100644 --- a/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java +++ b/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java @@ -1525,7 +1525,12 @@ private static NamedFunction createFunction(byte func) { } } + // Called from JVM when loading an AOT cache static { + runtimeSetup(); + } + + private static void runtimeSetup() { SharedSecrets.setJavaLangInvokeAccess(new JavaLangInvokeAccess() { @Override public Class getDeclaringClass(Object rmname) { diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/MethodHandleTest.java b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/MethodHandleTest.java index 84f89ebe215a8..12ace08df5e17 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/MethodHandleTest.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/MethodHandleTest.java @@ -33,16 +33,22 @@ * @build MethodHandleTest * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar mh.jar * MethodHandleTestApp MethodHandleTestApp$A MethodHandleTestApp$B + * UnsupportedBSMs UnsupportedBSMs$MyEnum + * ObjectMethodsTest ObjectMethodsTest$C * @run driver MethodHandleTest AOT */ +import java.io.Serializable; +import java.lang.invoke.CallSite; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.invoke.VarHandle; +import java.lang.runtime.ObjectMethods; import jdk.test.lib.cds.CDSAppTester; import jdk.test.lib.process.OutputAnalyzer; import jdk.test.lib.helpers.ClassFileInstaller; +import static java.lang.invoke.MethodType.methodType; public class MethodHandleTest { static final String appJar = ClassFileInstaller.getJarPath("mh.jar"); @@ -87,6 +93,7 @@ public String[] appCommandLine(RunMode runMode) { @Override public void checkExecution(OutputAnalyzer out, RunMode runMode) throws Exception { out.shouldHaveExitValue(0); + out.shouldContain("SwitchBootstraps.typeSwitch: 5678"); if (!runMode.isProductionRun()) { // MethodHandleTestApp should be initialized in the assembly phase as well, @@ -95,6 +102,7 @@ public void checkExecution(OutputAnalyzer out, RunMode runMode) throws Exception } else { // Make sure MethodHandleTestApp is aot-initialized in the production run. out.shouldNotContain("MethodHandleTestApp."); + out.shouldContain("intElm = 777"); } } } @@ -141,17 +149,29 @@ static class B { static VarHandle staticVH; static VarHandle instanceVH; + static MethodHandle arrayGetMH; + + // Created in assembly phase. + // Used in production run. + static MethodHandle ObjectMethodsTest_handle; + static { System.out.println("MethodHandleTestApp."); try { - setupCachedStatics(); + setupCachedMHs(); + ObjectMethodsTest_handle = ObjectMethodsTest.makeHandle(); + UnsupportedBSMs.invokeUnsupportedBSMs(); } catch (Throwable t) { throw new RuntimeException("Unexpected exception", t); } } - static void setupCachedStatics() throws Throwable { + // This method is executed during the assembly phase. + // + // Store some MHs into the AOT cache. Make sure they can be used during the production run. + // Also check that the class initialization order is consistent with specification. + static void setupCachedMHs() throws Throwable { MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); virtualMH = LOOKUP.findVirtual(A.class, "virtualMethod", MethodType.methodType(void.class)); instanceVH = LOOKUP.findVarHandle(B.class, "instanceField", long.class); @@ -161,11 +181,13 @@ static void setupCachedStatics() throws Throwable { A.staticMethod(); staticMH = LOOKUP.findStatic(A.class, "staticMethod", MethodType.methodType(void.class)); - // Make sure B is initialized before create staticVH, but the AOT-cached staticVH // should still include the init barrier even if B was initialized in the assembly phase. B.staticField += 5678; staticVH = LOOKUP.findStaticVarHandle(B.class, "staticField", long.class); + + // Array access MHs + arrayGetMH = MethodHandles.arrayElementGetter(int[].class); } private static Object invoke(MethodHandle mh, Object ... args) { @@ -184,8 +206,11 @@ public static void main(String[] args) throws Throwable { testMethodHandles(isProduction); testVarHandles(isProduction); - } + ObjectMethodsTest.testEqualsC(ObjectMethodsTest_handle); + + UnsupportedBSMs.invokeUnsupportedBSMs(); + } static void testMethodHandles(boolean isProduction) throws Throwable { state_A = 0; @@ -212,6 +237,14 @@ static void testMethodHandles(boolean isProduction) throws Throwable { throw new RuntimeException("state_A should be 6 but is: " + state_A); } } + + // (3) Test an array access MH + int[] intArray = new int[] {111, 222, 777}; + int intElm = (Integer)arrayGetMH.invoke(intArray, 2); + System.out.println("intElm = " + intElm); + if (intElm != 777) { + throw new RuntimeException("intElm should be 777 but is: " + intElm); + } } static void testVarHandles(boolean isProduction) throws Throwable { @@ -246,3 +279,116 @@ static void testVarHandles(boolean isProduction) throws Throwable { } } } + +// Excerpt from test/jdk/java/lang/runtime/ObjectMethodsTest.java +class ObjectMethodsTest { + public static class C { + static final MethodType EQUALS_DESC = methodType(boolean.class, C.class, Object.class); + static final MethodType HASHCODE_DESC = methodType(int.class, C.class); + static final MethodType TO_STRING_DESC = methodType(String.class, C.class); + + static final MethodHandle[] ACCESSORS = accessors(); + static final String NAME_LIST = "x;y"; + private static MethodHandle[] accessors() { + try { + return new MethodHandle[]{ + MethodHandles.lookup().findGetter(C.class, "x", int.class), + MethodHandles.lookup().findGetter(C.class, "y", int.class), + }; + } catch (Exception e) { + throw new AssertionError(e); + } + } + + private final int x; + private final int y; + C (int x, int y) { this.x = x; this.y = y; } + public int x() { return x; } + public int y() { return y; } + } + + public static MethodHandle makeHandle() throws Throwable { + MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + + CallSite cs = (CallSite)ObjectMethods.bootstrap(LOOKUP, "equals", C.EQUALS_DESC, C.class, C.NAME_LIST, C.ACCESSORS); + return cs.dynamicInvoker(); + } + + public static void testEqualsC(MethodHandle handle) throws Throwable { + C c = new C(5, 5); + assertTrue((boolean)handle.invokeExact(c, (Object)c)); + assertTrue((boolean)handle.invokeExact(c, (Object)new C(5, 5))); + assertFalse((boolean)handle.invokeExact(c, (Object)new C(5, 4))); + assertFalse((boolean)handle.invokeExact(c, (Object)new C(4, 5))); + assertFalse((boolean)handle.invokeExact(c, (Object)null)); + assertFalse((boolean)handle.invokeExact(c, new Object())); + } + + private static void assertTrue(boolean b) { + if (b != true) { + throw new RuntimeException("Assertion fails"); + } + } + + private static void assertFalse(boolean b) { + assertTrue(!b); + } +} + +class UnsupportedBSMs { + // This method is executed during the assembly phase. + // + // Try to invoke some BSMs that are normally not executed in the assembly phase. However, these + // BSMs may be executed in rare cases (such as when loading signed classes -- see JDK-8353330.) + // Let's make sure the assembly phase can tolerate such BSMs, even if the call sites that they + // produce are not stored into the AOT cache. + // + // Hopefully with enough testing in here, we can avoid situations where innocent changes in + // core libs might cause the AOT assembly phase to fail. + static void invokeUnsupportedBSMs() throws Throwable { + int n = testTypeSwitch((Integer)1234); + System.out.println("SwitchBootstraps.typeSwitch: " + n); + if (n != 5678) { + throw new RuntimeException("n should be " + 5678 + " but is: " + n); + } + + Object o = getRunnableAndSerializable(); + System.out.println(o.getClass()); + if (!(o instanceof Runnable) || !(o instanceof Serializable)) { + throw new RuntimeException("o has wrong interfaces"); + } + + statementEnum(MyEnum.A); + } + + static int testTypeSwitch(Number n) { + // BSM = java/lang/runtime/SwitchBootstraps::typeSwitch + return switch (n) { + case Integer in -> { + yield 5678; + } + default -> { + yield 0; + } + }; + } + + static Runnable getRunnableAndSerializable() { + // BSM = java/lang/invoke/LambdaMetafactory.altMetafactory + return (Runnable & Serializable) () -> { + System.out.println("Inside getRunnableAndSerializable"); + }; + } + + // Excerpt from test/langtools/tools/javac/patterns/EnumTypeChanges.java + enum MyEnum { A, B; } + static String statementEnum(MyEnum e) { + // BSM = java/lang/runtime/SwitchBootstraps.enumSwitch + switch (e) { + case A -> { return "A"; } + case B -> { return "B"; } + case MyEnum e1 when e1 == null -> throw new AssertionError(); + default -> { return "D"; } + } + } +}