-
Notifications
You must be signed in to change notification settings - Fork 5.8k
8354890: AOT-initialize j.l.i.MethodHandleImpl and inner classes #24956
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
a23331a
c6beb85
2d82e36
dc5ad26
2544cc7
6678482
1898399
73786f2
2c31ca7
643eec2
bba16ee
a25aab0
331acfa
7021656
01f0ea0
d0929ac
31bbf4c
0f6a2e0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -1525,7 +1525,12 @@ private static NamedFunction createFunction(byte func) { | |||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
// Called from JVM when loading an AOT cache | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
static { | ||||||||||||
runtimeSetup(); | ||||||||||||
} | ||||||||||||
|
||||||||||||
private static void runtimeSetup() { | ||||||||||||
Comment on lines
+1532
to
+1533
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same problem in Reference, credit to @ExE-Boss |
||||||||||||
SharedSecrets.setJavaLangInvokeAccess(new JavaLangInvokeAccess() { | ||||||||||||
@Override | ||||||||||||
public Class<?> getDeclaringClass(Object rmname) { | ||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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.<clinit>"); | ||
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.<clinit>"); | ||
|
||
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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. String s = statementEnum(MyEnum.A);
if (!s.equals("A")) {
throw new RuntimeException("enum switch incorrect");
} |
||
} | ||
|
||
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"; } | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change is to avoid deadlock in the previous implementation.