1 /*
2  * Copyright (C) 2016 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 class Main1 {
getName()18   String getName() {
19     return "Main1";
20   }
21 
printError(String msg)22   void printError(String msg) {
23     System.out.println(msg);
24   }
25 
foo(int i)26   void foo(int i) {
27     if (i != 1) {
28       printError("error1");
29     }
30   }
31 
getValue1()32   int getValue1() {
33     return 1;
34   }
getValue2()35   int getValue2() {
36     return 2;
37   }
getValue3()38   int getValue3() {
39     return 3;
40   }
getValue4()41   int getValue4() {
42     return 4;
43   }
getValue5()44   int getValue5() {
45     return 5;
46   }
getValue6()47   int getValue6() {
48     return 6;
49   }
50 }
51 
52 class Main2 extends Main1 {
getName()53   String getName() {
54     return "Main2";
55   }
56 
foo(int i)57   void foo(int i) {
58     if (i != 2) {
59       printError("error2");
60     }
61   }
62 }
63 
64 class Main3 extends Main1 {
getName()65   String getName() {
66     return "Main3";
67   }
68 }
69 
70 public class Main {
71   static Main1 sMain1;
72   static Main1 sMain2;
73 
74   static boolean sIsOptimizing = true;
75   static boolean sHasJIT = true;
76   static volatile boolean sOtherThreadStarted;
77 
78   // sMain1.foo() will be always be Main1.foo() before Main2 is loaded/linked.
79   // So sMain1.foo() can be devirtualized to Main1.foo() and be inlined.
80   // After Helper.createMain2() which links in Main2, live testOverride() on stack
81   // should be deoptimized.
testOverride(boolean createMain2, boolean wait, boolean setHasJIT)82   static void testOverride(boolean createMain2, boolean wait, boolean setHasJIT) {
83     if (setHasJIT) {
84       if (isInterpreted()) {
85         sHasJIT = false;
86       }
87       return;
88     }
89 
90     if (createMain2 && (sIsOptimizing || sHasJIT)) {
91       assertIsManaged();
92     }
93 
94     sMain1.foo(sMain1.getClass() == Main1.class ? 1 : 2);
95 
96     if (createMain2) {
97       // Wait for the other thread to start.
98       while (!sOtherThreadStarted);
99       // Create an Main2 instance and assign it to sMain2.
100       // sMain1 is kept the same.
101       sMain2 = Helper.createMain2();
102       // Wake up the other thread.
103       synchronized(Main.class) {
104         Main.class.notify();
105       }
106     } else if (wait) {
107       // This is the other thread.
108       synchronized(Main.class) {
109         sOtherThreadStarted = true;
110         // Wait for Main2 to be linked and deoptimization is triggered.
111         try {
112           Main.class.wait();
113         } catch (Exception e) {
114         }
115       }
116     }
117 
118     // There should be a deoptimization here right after Main2 is linked by
119     // calling Helper.createMain2(), even though sMain1 didn't change.
120     // The behavior here would be different if inline-cache is used, which
121     // doesn't deoptimize since sMain1 still hits the type cache.
122     sMain1.foo(sMain1.getClass() == Main1.class ? 1 : 2);
123     if ((createMain2 || wait) && sHasJIT && !sIsOptimizing) {
124       // This method should be deoptimized right after Main2 is created.
125       assertIsInterpreted();
126     }
127 
128     if (sMain2 != null) {
129       sMain2.foo(sMain2.getClass() == Main1.class ? 1 : 2);
130     }
131   }
132 
133   static Main1[] sArray;
134 
calcValue(Main1 m)135   static long calcValue(Main1 m) {
136     return m.getValue1()
137         + m.getValue2() * 2
138         + m.getValue3() * 3
139         + m.getValue4() * 4
140         + m.getValue5() * 5
141         + m.getValue6() * 6;
142   }
143 
testNoOverrideLoop(int count)144   static long testNoOverrideLoop(int count) {
145     long sum = 0;
146     for (int i=0; i<count; i++) {
147       sum += calcValue(sArray[0]);
148       sum += calcValue(sArray[1]);
149       sum += calcValue(sArray[2]);
150     }
151     return sum;
152   }
153 
testNoOverride()154   static void testNoOverride() {
155     sArray = new Main1[3];
156     sArray[0] = new Main1();
157     sArray[1] = Helper.createMain2();
158     sArray[2] = Helper.createMain3();
159     long sum = 0;
160     // Loop enough to get methods JITed.
161     for (int i=0; i<100; i++) {
162       testNoOverrideLoop(1);
163     }
164     ensureJitCompiled(Main.class, "testNoOverrideLoop");
165     ensureJitCompiled(Main.class, "calcValue");
166 
167     long t1 = System.currentTimeMillis();
168     sum = testNoOverrideLoop(100000);
169     long t2 = System.currentTimeMillis();
170     if (sum != 27300000L) {
171       System.out.println("Unexpected result.");
172     }
173   }
174 
assertSingleImplementation(Class<?> clazz, String method_name, boolean b)175   private static void assertSingleImplementation(Class<?> clazz, String method_name, boolean b) {
176     if (hasSingleImplementation(clazz, method_name) != b) {
177       System.out.println(clazz + "." + method_name +
178           " doesn't have single implementation value of " + b);
179     }
180   }
181 
182   // Test scenarios under which CHA-based devirtualization happens,
183   // and class loading that overrides a method can invalidate compiled code.
184   // Also test pure non-overriding case, which is more for checking generated
185   // code form.
main(String[] args)186   public static void main(String[] args) {
187     System.loadLibrary(args[0]);
188 
189     // CHeck some boot-image methods.
190 
191     // We would want to have this, but currently setting single-implementation in the boot image
192     // does not work well with app images. b/34193647
193     final boolean ARRAYLIST_SIZE_EXPECTED = false;
194     assertSingleImplementation(java.util.ArrayList.class, "size", ARRAYLIST_SIZE_EXPECTED);
195 
196     // java.util.LinkedHashMap overrides get().
197     assertSingleImplementation(java.util.HashMap.class, "get", false);
198 
199     // We don't set single-implementation modifier bit for final classes or methods
200     // since we can devirtualize without CHA for those cases. However hasSingleImplementation()
201     // should return true for those cases.
202     assertSingleImplementation(java.lang.String.class, "charAt", true);
203     assertSingleImplementation(java.lang.Thread.class, "join", true);
204 
205     if (isInterpreted()) {
206       sIsOptimizing = false;
207     }
208 
209     // sMain1 is an instance of Main1. Main2 hasn't bee loaded yet.
210     sMain1 = new Main1();
211 
212     ensureJitCompiled(Main.class, "testOverride");
213     testOverride(false, false, true);
214 
215     if (sHasJIT && !sIsOptimizing) {
216       assertSingleImplementation(Main1.class, "foo", true);
217     } else {
218       // Main2 is verified ahead-of-time so it's linked in already.
219     }
220     assertSingleImplementation(Main1.class, "getValue1", true);
221 
222     // Create another thread that also calls sMain1.foo().
223     // Try to test suspend and deopt another thread.
224     new Thread() {
225       public void run() {
226         testOverride(false, true, false);
227       }
228     }.start();
229 
230     // This will create Main2 instance in the middle of testOverride().
231     testOverride(true, false, false);
232     assertSingleImplementation(Main1.class, "foo", false);
233     assertSingleImplementation(Main1.class, "getValue1", true);
234 
235     testNoOverride();
236   }
237 
ensureJitCompiled(Class<?> itf, String method_name)238   private static native void ensureJitCompiled(Class<?> itf, String method_name);
assertIsInterpreted()239   private static native void assertIsInterpreted();
assertIsManaged()240   private static native void assertIsManaged();
isInterpreted()241   private static native boolean isInterpreted();
hasSingleImplementation(Class<?> clazz, String method_name)242   private static native boolean hasSingleImplementation(Class<?> clazz, String method_name);
243 }
244 
245 // Put createMain2() in another class to avoid class loading due to verifier.
246 class Helper {
createMain2()247   static Main1 createMain2() {
248     return new Main2();
249   }
createMain3()250   static Main1 createMain3() {
251     return new Main3();
252   }
253 }
254