1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 abstract class Base {
foo(int i)18   abstract void foo(int i);
19 
printError(String msg)20   void printError(String msg) {
21     System.out.println(msg);
22   }
23 }
24 
25 class Main1 extends Base {
foo(int i)26   void foo(int i) {
27     if (i != 1) {
28       printError("error1");
29     }
30   }
31 }
32 
33 class Main2 extends Main1 {
foo(int i)34   void foo(int i) {
35     if (i != 2) {
36       printError("error2");
37     }
38   }
39 }
40 
41 public class Main {
42   static Base sMain1;
43   static Base sMain2;
44 
45   static boolean sIsOptimizing = true;
46   static boolean sHasJIT = true;
47   static volatile boolean sOtherThreadStarted;
48 
assertSingleImplementation(Class<?> clazz, String method_name, boolean b)49   private static void assertSingleImplementation(Class<?> clazz, String method_name, boolean b) {
50     if (hasSingleImplementation(clazz, method_name) != b) {
51       System.out.println(clazz + "." + method_name +
52           " doesn't have single implementation value of " + b);
53     }
54   }
55 
56   // sMain1.foo() will be always be Main1.foo() before Main2 is loaded/linked.
57   // So sMain1.foo() can be devirtualized to Main1.foo() and be inlined.
58   // After Helper.createMain2() which links in Main2, live testOverride() on stack
59   // should be deoptimized.
testOverride(boolean createMain2, boolean wait, boolean setHasJIT)60   static void testOverride(boolean createMain2, boolean wait, boolean setHasJIT) {
61     if (setHasJIT) {
62       if (isInterpreted()) {
63         sHasJIT = false;
64       }
65       return;
66     }
67 
68     if (createMain2 && (sIsOptimizing || sHasJIT)) {
69       assertIsManaged();
70     }
71 
72     sMain1.foo(sMain1.getClass() == Main1.class ? 1 : 2);
73 
74     if (createMain2) {
75       // Wait for the other thread to start.
76       while (!sOtherThreadStarted);
77       // Create an Main2 instance and assign it to sMain2.
78       // sMain1 is kept the same.
79       sMain2 = Helper.createMain2();
80       // Wake up the other thread.
81       synchronized(Main.class) {
82         Main.class.notify();
83       }
84     } else if (wait) {
85       // This is the other thread.
86       synchronized(Main.class) {
87         sOtherThreadStarted = true;
88         // Wait for Main2 to be linked and deoptimization is triggered.
89         try {
90           Main.class.wait();
91         } catch (Exception e) {
92         }
93       }
94     }
95 
96     // There should be a deoptimization here right after Main2 is linked by
97     // calling Helper.createMain2(), even though sMain1 didn't change.
98     // The behavior here would be different if inline-cache is used, which
99     // doesn't deoptimize since sMain1 still hits the type cache.
100     sMain1.foo(sMain1.getClass() == Main1.class ? 1 : 2);
101     if ((createMain2 || wait) && sHasJIT && !sIsOptimizing) {
102       // This method should be deoptimized right after Main2 is created.
103       assertIsInterpreted();
104     }
105 
106     if (sMain2 != null) {
107       sMain2.foo(sMain2.getClass() == Main1.class ? 1 : 2);
108     }
109   }
110 
111   // Test scenarios under which CHA-based devirtualization happens,
112   // and class loading that overrides a method can invalidate compiled code.
main(String[] args)113   public static void main(String[] args) {
114     System.loadLibrary(args[0]);
115 
116     if (isInterpreted()) {
117       sIsOptimizing = false;
118     }
119 
120     // sMain1 is an instance of Main1. Main2 hasn't bee loaded yet.
121     sMain1 = new Main1();
122 
123     ensureJitCompiled(Main.class, "testOverride");
124     testOverride(false, false, true);
125 
126     if (sHasJIT && !sIsOptimizing) {
127       assertSingleImplementation(Base.class, "foo", true);
128       assertSingleImplementation(Main1.class, "foo", true);
129     } else {
130       // Main2 is verified ahead-of-time so it's linked in already.
131     }
132 
133     // Create another thread that also calls sMain1.foo().
134     // Try to test suspend and deopt another thread.
135     new Thread() {
136       public void run() {
137         testOverride(false, true, false);
138       }
139     }.start();
140 
141     // This will create Main2 instance in the middle of testOverride().
142     testOverride(true, false, false);
143     assertSingleImplementation(Base.class, "foo", false);
144     assertSingleImplementation(Main1.class, "foo", false);
145   }
146 
ensureJitCompiled(Class<?> itf, String method_name)147   private static native void ensureJitCompiled(Class<?> itf, String method_name);
assertIsInterpreted()148   private static native void assertIsInterpreted();
assertIsManaged()149   private static native void assertIsManaged();
isInterpreted()150   private static native boolean isInterpreted();
hasSingleImplementation(Class<?> clazz, String method_name)151   private static native boolean hasSingleImplementation(Class<?> clazz, String method_name);
152 }
153 
154 // Put createMain2() in another class to avoid class loading due to verifier.
155 class Helper {
createMain2()156   static Main1 createMain2() {
157     return new Main2();
158   }
159 }
160