Fix ClinitCheck pruning.
Make sure we merge the ClinitCheck only with LoadClass and
HInvokeStaticOrDirect that is a part of the very same dex
instruction. This fixes incorrect stack traces from class
initializers (wrong dex pcs).
Rewrite the pruning to do all the ClinitCheck merging when
we see the ClinitCheck, instead of merging ClinitCheck into
LoadClass and then LoadClass into HInvokeStaticOrDirect.
When we later see an HInvokeStaticOrDirect with an explicit
check (i.e. not merged), we know that some other instruction
is doing the check and the invoke doesn't need to, so we
mark it as not requiring the check at all. (Previously it
would have been marked as having an implicit check.)
Remove the restriction on merging with inlined invoke static
as this is not necessary anymore. This was a workaround for
X.test():
invoke-static C.foo() [1]
C.foo():
invoke-static C.bar() [2]
After inlining and GVN we have
X.test():
LoadClass C (from [1])
ClinitCheck C (from [1], to be merged to LoadClass)
InvokeStaticOrDirect C.bar() (from [2])
and the LoadClass must not be merged into the invoke as this
would cause the resolution trampoline to see an inlined
frame from the not-yet-loaded class C during the stack walk
and try to load the class. However, we're not allowed to
load new classes at that point, so an attempt to do so leads
to an assertion failure. With this CL, LoadClass is not
merged when it comes from a different instruction, so we can
guarantee that all inlined frames seen by the stack walk in
the resolution trampoline belong to already loaded classes.
Change-Id: I2b8da8d4f295355dce17141f0fab2dace126684d
diff --git a/test/478-checker-clinit-check-pruning/src/Main.java b/test/478-checker-clinit-check-pruning/src/Main.java
index cff6273..7993513 100644
--- a/test/478-checker-clinit-check-pruning/src/Main.java
+++ b/test/478-checker-clinit-check-pruning/src/Main.java
@@ -83,7 +83,7 @@
// before the next pass (liveness analysis) instead.
/// CHECK-START: void Main.invokeStaticNotInlined() liveness (before)
- /// CHECK: InvokeStaticOrDirect
+ /// CHECK: InvokeStaticOrDirect clinit_check:implicit
/// CHECK-START: void Main.invokeStaticNotInlined() liveness (before)
/// CHECK-NOT: LoadClass
@@ -269,7 +269,7 @@
/// CHECK-START: void Main.noClinitBecauseOfInvokeStatic() liveness (before)
/// CHECK-DAG: <<IntConstant:i\d+>> IntConstant 0
/// CHECK-DAG: <<LoadClass:l\d+>> LoadClass gen_clinit_check:false
- /// CHECK-DAG: InvokeStaticOrDirect
+ /// CHECK-DAG: InvokeStaticOrDirect clinit_check:implicit
/// CHECK-DAG: StaticFieldSet [<<LoadClass>>,<<IntConstant>>]
/// CHECK-START: void Main.noClinitBecauseOfInvokeStatic() liveness (before)
@@ -289,7 +289,7 @@
/// CHECK-DAG: <<IntConstant:i\d+>> IntConstant 0
/// CHECK-DAG: <<LoadClass:l\d+>> LoadClass gen_clinit_check:true
/// CHECK-DAG: StaticFieldSet [<<LoadClass>>,<<IntConstant>>]
- /// CHECK-DAG: InvokeStaticOrDirect
+ /// CHECK-DAG: InvokeStaticOrDirect clinit_check:none
/// CHECK-START: void Main.clinitBecauseOfFieldAccess() liveness (before)
/// CHECK-NOT: ClinitCheck
@@ -298,6 +298,206 @@
ClassWithClinit2.$noinline$staticMethod();
}
+ /*
+ * Verify that LoadClass from const-class is not merged with
+ * later invoke-static (or it's ClinitCheck).
+ */
+
+ /// CHECK-START: void Main.constClassAndInvokeStatic(java.lang.Iterable) liveness (before)
+ /// CHECK: LoadClass gen_clinit_check:false
+ /// CHECK: InvokeStaticOrDirect clinit_check:implicit
+
+ /// CHECK-START: void Main.constClassAndInvokeStatic(java.lang.Iterable) liveness (before)
+ /// CHECK-NOT: ClinitCheck
+
+ static void constClassAndInvokeStatic(Iterable it) {
+ $opt$inline$ignoreClass(ClassWithClinit7.class);
+ ClassWithClinit7.someStaticMethod(it);
+ }
+
+ static void $opt$inline$ignoreClass(Class c) {
+ }
+
+ static class ClassWithClinit7 {
+ static {
+ System.out.println("Main$ClassWithClinit7's static initializer");
+ }
+
+ // Note: not inlined from constClassAndInvokeStatic() but fully inlined from main().
+ static void someStaticMethod(Iterable it) {
+ // We're not inlining invoke-interface at the moment.
+ it.iterator();
+ }
+ }
+
+ /*
+ * Verify that LoadClass from sget is not merged with later invoke-static.
+ */
+
+ /// CHECK-START: void Main.sgetAndInvokeStatic(java.lang.Iterable) liveness (before)
+ /// CHECK: LoadClass gen_clinit_check:true
+ /// CHECK: InvokeStaticOrDirect clinit_check:none
+
+ /// CHECK-START: void Main.sgetAndInvokeStatic(java.lang.Iterable) liveness (before)
+ /// CHECK-NOT: ClinitCheck
+
+ static void sgetAndInvokeStatic(Iterable it) {
+ $opt$inline$ignoreInt(ClassWithClinit8.value);
+ ClassWithClinit8.someStaticMethod(it);
+ }
+
+ static void $opt$inline$ignoreInt(int i) {
+ }
+
+ static class ClassWithClinit8 {
+ public static int value = 0;
+ static {
+ System.out.println("Main$ClassWithClinit8's static initializer");
+ }
+
+ // Note: not inlined from sgetAndInvokeStatic() but fully inlined from main().
+ static void someStaticMethod(Iterable it) {
+ // We're not inlining invoke-interface at the moment.
+ it.iterator();
+ }
+ }
+
+ /*
+ * Verify that LoadClass from const-class, ClinitCheck from sget and
+ * InvokeStaticOrDirect from invoke-static are not merged.
+ */
+
+ /// CHECK-START: void Main.constClassSgetAndInvokeStatic(java.lang.Iterable) liveness (before)
+ /// CHECK: LoadClass gen_clinit_check:false
+ /// CHECK: ClinitCheck
+ /// CHECK: InvokeStaticOrDirect clinit_check:none
+
+ static void constClassSgetAndInvokeStatic(Iterable it) {
+ $opt$inline$ignoreClass(ClassWithClinit9.class);
+ $opt$inline$ignoreInt(ClassWithClinit9.value);
+ ClassWithClinit9.someStaticMethod(it);
+ }
+
+ static class ClassWithClinit9 {
+ public static int value = 0;
+ static {
+ System.out.println("Main$ClassWithClinit9's static initializer");
+ }
+
+ // Note: not inlined from constClassSgetAndInvokeStatic() but fully inlined from main().
+ static void someStaticMethod(Iterable it) {
+ // We're not inlining invoke-interface at the moment.
+ it.iterator();
+ }
+ }
+
+ /*
+ * Verify that LoadClass from a fully-inlined invoke-static is not merged
+ * with InvokeStaticOrDirect from a later invoke-static to the same method.
+ */
+
+ /// CHECK-START: void Main.inlinedInvokeStaticViaNonStatic(java.lang.Iterable) liveness (before)
+ /// CHECK: LoadClass gen_clinit_check:true
+ /// CHECK: InvokeStaticOrDirect clinit_check:none
+
+ /// CHECK-START: void Main.inlinedInvokeStaticViaNonStatic(java.lang.Iterable) liveness (before)
+ /// CHECK-NOT: ClinitCheck
+
+ static void inlinedInvokeStaticViaNonStatic(Iterable it) {
+ inlinedInvokeStaticViaNonStaticHelper(null);
+ inlinedInvokeStaticViaNonStaticHelper(it);
+ }
+
+ static void inlinedInvokeStaticViaNonStaticHelper(Iterable it) {
+ ClassWithClinit10.inlinedForNull(it);
+ }
+
+ static class ClassWithClinit10 {
+ public static int value = 0;
+ static {
+ System.out.println("Main$ClassWithClinit10's static initializer");
+ }
+
+ static void inlinedForNull(Iterable it) {
+ if (it != null) {
+ // We're not inlining invoke-interface at the moment.
+ it.iterator();
+ }
+ }
+ }
+
+ /*
+ * Check that the LoadClass from an invoke-static C.foo() doesn't get merged with
+ * an invoke-static inside C.foo(). This would mess up the stack walk in the
+ * resolution trampoline where we would have to load C (if C isn't loaded yet)
+ * which is not permitted there.
+ *
+ * Note: In case of failure, we would get an failed assertion during compilation,
+ * so we wouldn't really get to the checker tests below.
+ */
+
+ /// CHECK-START: void Main.inlinedInvokeStaticViaStatic(java.lang.Iterable) liveness (before)
+ /// CHECK: LoadClass gen_clinit_check:true
+ /// CHECK: InvokeStaticOrDirect clinit_check:none
+
+ /// CHECK-START: void Main.inlinedInvokeStaticViaStatic(java.lang.Iterable) liveness (before)
+ /// CHECK-NOT: ClinitCheck
+
+ static void inlinedInvokeStaticViaStatic(Iterable it) {
+ ClassWithClinit11.callInlinedForNull(it);
+ }
+
+ static class ClassWithClinit11 {
+ public static int value = 0;
+ static {
+ System.out.println("Main$ClassWithClinit11's static initializer");
+ }
+
+ static void callInlinedForNull(Iterable it) {
+ inlinedForNull(it);
+ }
+
+ static void inlinedForNull(Iterable it) {
+ // We're not inlining invoke-interface at the moment.
+ it.iterator();
+ }
+ }
+
+ /*
+ * A test similar to inlinedInvokeStaticViaStatic() but doing the indirect invoke
+ * twice with the first one to be fully inlined.
+ */
+
+ /// CHECK-START: void Main.inlinedInvokeStaticViaStaticTwice(java.lang.Iterable) liveness (before)
+ /// CHECK: LoadClass gen_clinit_check:true
+ /// CHECK: InvokeStaticOrDirect clinit_check:none
+
+ /// CHECK-START: void Main.inlinedInvokeStaticViaStaticTwice(java.lang.Iterable) liveness (before)
+ /// CHECK-NOT: ClinitCheck
+
+ static void inlinedInvokeStaticViaStaticTwice(Iterable it) {
+ ClassWithClinit12.callInlinedForNull(null);
+ ClassWithClinit12.callInlinedForNull(it);
+ }
+
+ static class ClassWithClinit12 {
+ public static int value = 0;
+ static {
+ System.out.println("Main$ClassWithClinit12's static initializer");
+ }
+
+ static void callInlinedForNull(Iterable it) {
+ inlinedForNull(it);
+ }
+
+ static void inlinedForNull(Iterable it) {
+ if (it != null) {
+ // We're not inlining invoke-interface at the moment.
+ it.iterator();
+ }
+ }
+ }
+
// TODO: Add a test for the case of a static method whose declaring
// class type index is not available (i.e. when `storage_index`
// equals `DexFile::kDexNoIndex` in
@@ -310,5 +510,12 @@
ClassWithClinit4.invokeStaticNotInlined();
SubClassOfClassWithClinit5.invokeStaticInlined();
SubClassOfClassWithClinit6.invokeStaticNotInlined();
+ Iterable it = new Iterable() { public java.util.Iterator iterator() { return null; } };
+ constClassAndInvokeStatic(it);
+ sgetAndInvokeStatic(it);
+ constClassSgetAndInvokeStatic(it);
+ inlinedInvokeStaticViaNonStatic(it);
+ inlinedInvokeStaticViaStatic(it);
+ inlinedInvokeStaticViaStaticTwice(it);
}
}