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 class Main1 implements Base {
18 }
19 
20 class Main2 extends Main1 {
foobar()21     public void foobar() {}
22 }
23 
24 class Main3 implements Base {
foo(int i)25     public int foo(int i) {
26         if (i != 3) {
27             printError("error3");
28         }
29         return -(i + 10);
30     }
31 }
32 
33 public class Main {
34     static Base sMain1;
35     static Base sMain2;
36     static Base sMain3;
37 
38     static boolean sIsOptimizing = true;
39     static boolean sHasJIT = true;
40     static volatile boolean sOtherThreadStarted;
41 
assertSingleImplementation(Class<?> clazz, String method_name, boolean b)42     private static void assertSingleImplementation(Class<?> clazz, String method_name, boolean b) {
43         if (hasSingleImplementation(clazz, method_name) != b) {
44             System.out.println(clazz + "." + method_name +
45                     " doesn't have single implementation value of " + b);
46         }
47     }
48 
getValue(Class<?> cls)49     static int getValue(Class<?> cls) {
50         if (cls == Main1.class || cls == Main2.class) {
51             return 1;
52         }
53         return 3;
54     }
55 
56     // sMain1.foo()/sMain2.foo() will be always be Base.foo() before Main3 is loaded/linked.
57     // So sMain1.foo() can be devirtualized to Base.foo() and be inlined.
58     // After Helper.createMain3() which links in Main3, live testImplement() on stack
59     // should be deoptimized.
testImplement(boolean createMain3, boolean wait, boolean setHasJIT)60     static void testImplement(boolean createMain3, boolean wait, boolean setHasJIT) {
61         if (setHasJIT) {
62             if (isInterpreted()) {
63                 sHasJIT = false;
64             }
65             return;
66         }
67 
68         if (createMain3 && (sIsOptimizing || sHasJIT)) {
69             assertIsManaged();
70         }
71 
72         if (sMain1.foo(getValue(sMain1.getClass())) != 11) {
73             System.out.println("11 expected.");
74         }
75         if (sMain1.$noinline$bar() != -1) {
76             System.out.println("-1 expected.");
77         }
78         if (sMain2.foo(getValue(sMain2.getClass())) != 11) {
79             System.out.println("11 expected.");
80         }
81 
82         if (createMain3) {
83             // Wait for the other thread to start.
84             while (!sOtherThreadStarted);
85             // Create an Main2 instance and assign it to sMain2.
86             // sMain1 is kept the same.
87             sMain3 = Helper.createMain3();
88             // Wake up the other thread.
89             synchronized(Main.class) {
90                 Main.class.notify();
91             }
92         } else if (wait) {
93             // This is the other thread.
94             synchronized(Main.class) {
95                 sOtherThreadStarted = true;
96                 // Wait for Main2 to be linked and deoptimization is triggered.
97                 try {
98                     Main.class.wait();
99                 } catch (Exception e) {
100                 }
101             }
102         }
103 
104         // There should be a deoptimization here right after Main3 is linked by
105         // calling Helper.createMain3(), even though sMain1 didn't change.
106         // The behavior here would be different if inline-cache is used, which
107         // doesn't deoptimize since sMain1 still hits the type cache.
108         if (sMain1.foo(getValue(sMain1.getClass())) != 11) {
109             System.out.println("11 expected.");
110         }
111         if ((createMain3 || wait) && sHasJIT && !sIsOptimizing) {
112             // This method should be deoptimized right after Main3 is created.
113             assertIsInterpreted();
114         }
115 
116         if (sMain3 != null) {
117             if (sMain3.foo(getValue(sMain3.getClass())) != -13) {
118                 System.out.println("-13 expected.");
119             }
120         }
121     }
122 
123     // Test scenarios under which CHA-based devirtualization happens,
124     // and class loading that implements a method can invalidate compiled code.
main(String[] args)125     public static void main(String[] args) {
126         System.loadLibrary(args[0]);
127 
128         if (isInterpreted()) {
129             sIsOptimizing = false;
130         }
131 
132         // sMain1 is an instance of Main1.
133         // sMain2 is an instance of Main2.
134         // Neither Main1 nor Main2 override default method Base.foo().
135         // Main3 hasn't bee loaded yet.
136         sMain1 = new Main1();
137         sMain2 = new Main2();
138 
139         ensureJitCompiled(Main.class, "testImplement");
140         testImplement(false, false, true);
141 
142         if (sHasJIT && !sIsOptimizing) {
143             assertSingleImplementation(Base.class, "foo", true);
144             assertSingleImplementation(Main1.class, "foo", true);
145         } else {
146             // Main3 is verified ahead-of-time so it's linked in already.
147         }
148 
149         // Create another thread that also calls sMain1.foo().
150         // Try to test suspend and deopt another thread.
151         new Thread() {
152             public void run() {
153                 testImplement(false, true, false);
154             }
155         }.start();
156 
157         // This will create Main3 instance in the middle of testImplement().
158         testImplement(true, false, false);
159         assertSingleImplementation(Base.class, "foo", false);
160         assertSingleImplementation(Main1.class, "foo", true);
161         assertSingleImplementation(sMain3.getClass(), "foo", true);
162     }
163 
ensureJitCompiled(Class<?> itf, String method_name)164     private static native void ensureJitCompiled(Class<?> itf, String method_name);
assertIsInterpreted()165     private static native void assertIsInterpreted();
assertIsManaged()166     private static native void assertIsManaged();
isInterpreted()167     private static native boolean isInterpreted();
hasSingleImplementation(Class<?> clazz, String method_name)168     private static native boolean hasSingleImplementation(Class<?> clazz, String method_name);
169 }
170 
171 // Put createMain3() in another class to avoid class loading due to verifier.
172 class Helper {
createMain3()173     static Base createMain3() {
174         return new Main3();
175     }
176 }
177