blob: 787318dd6785f67e43b5e06229210270dc6694a7 [file] [log] [blame]
Mingyao Yang063fc772016-08-02 11:02:54 -07001/*
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
17class Main1 {
18 String getName() {
19 return "Main1";
20 }
21
22 void printError(String msg) {
23 System.out.println(msg);
24 }
25
26 void foo(int i) {
27 if (i != 1) {
28 printError("error1");
29 }
30 }
31
32 int getValue1() {
33 return 1;
34 }
35 int getValue2() {
36 return 2;
37 }
38 int getValue3() {
39 return 3;
40 }
41 int getValue4() {
42 return 4;
43 }
44 int getValue5() {
45 return 5;
46 }
47 int getValue6() {
48 return 6;
49 }
50}
51
52class Main2 extends Main1 {
53 String getName() {
54 return "Main2";
55 }
56
57 void foo(int i) {
58 if (i != 2) {
59 printError("error2");
60 }
61 }
62}
63
64class Main3 extends Main1 {
65 String getName() {
66 return "Main3";
67 }
68}
69
70public 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 Dummy.createMain2() which links in Main2, live testOverride() on stack
81 // should be deoptimized.
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 = Dummy.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 Dummy.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
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
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
154 static void testNoOverride() {
155 sArray = new Main1[3];
156 sArray[0] = new Main1();
157 sArray[1] = Dummy.createMain2();
158 sArray[2] = Dummy.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
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 scanerios 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.
186 public static void main(String[] args) {
187 System.loadLibrary(args[0]);
188
189 // CHeck some boot-image methods.
190 assertSingleImplementation(java.util.ArrayList.class, "size", true);
191 // java.util.LinkedHashMap overrides get().
192 assertSingleImplementation(java.util.HashMap.class, "get", false);
193
194 // We don't set single-implementation modifier bit for final classes or methods
195 // since we can devirtualize without CHA for those cases. However hasSingleImplementation()
196 // should return true for those cases.
197 assertSingleImplementation(java.lang.String.class, "charAt", true);
198 assertSingleImplementation(java.lang.Thread.class, "join", true);
199 // We don't set single-implementation modifier bit for native methods.
200 assertSingleImplementation(java.lang.Thread.class, "isInterrupted", false);
201
202 if (isInterpreted()) {
203 sIsOptimizing = false;
204 }
205
206 // sMain1 is an instance of Main1. Main2 hasn't bee loaded yet.
207 sMain1 = new Main1();
208
209 // Loop enough to get testOverride() JITed.
210 for (int i=0; i<100; i++) {
211 testOverride(false, false, false);
212 }
213
214 ensureJitCompiled(Main.class, "testOverride");
215 testOverride(false, false, true);
216
217 if (sHasJIT && !sIsOptimizing) {
218 assertSingleImplementation(Main1.class, "foo", true);
219 } else {
220 // Main2 is verified ahead-of-time so it's linked in already.
221 }
222 assertSingleImplementation(Main1.class, "getValue1", true);
223
224 // Create another thread that also calls sMain1.foo().
225 // Try to test suspend and deopt another thread.
226 new Thread() {
227 public void run() {
228 testOverride(false, true, false);
229 }
230 }.start();
231
232 // This will create Main2 instance in the middle of testOverride().
233 testOverride(true, false, false);
234 assertSingleImplementation(Main1.class, "foo", false);
235 assertSingleImplementation(Main1.class, "getValue1", true);
236
237 testNoOverride();
238 }
239
240 private static native void ensureJitCompiled(Class<?> itf, String method_name);
241 private static native void assertIsInterpreted();
242 private static native void assertIsManaged();
243 private static native boolean isInterpreted();
244 private static native boolean hasSingleImplementation(Class<?> clazz, String method_name);
245}
246
247// Do it in another class to avoid class loading due to verifier.
248class Dummy {
249 static Main1 createMain2() {
250 return new Main2();
251 }
252 static Main1 createMain3() {
253 return new Main3();
254 }
255}